diff --git a/packages/grafana-ui/src/components/Forms/Form.mdx b/packages/grafana-ui/src/components/Forms/Form.mdx
index fdfcb66dec3..417eb7c4105 100644
--- a/packages/grafana-ui/src/components/Forms/Form.mdx
+++ b/packages/grafana-ui/src/components/Forms/Form.mdx
@@ -1,4 +1,4 @@
-import { Meta, Story, Preview, Props } from "@storybook/addon-docs/blocks";
+import { Meta, Props } from "@storybook/addon-docs/blocks";
import { Form } from "./Form";
@@ -29,8 +29,8 @@ const defaultUser: Partial = {
>{({register, errors}) => {
return (
-
-
+
+
)
@@ -43,18 +43,17 @@ const defaultUser: Partial = {
#### `register`
-`register` allows to register form elements(inputs, selects, radios, etc) in the form. In order to do that you need to pass `register` as a `ref` property to the form input. For example:
+`register` allows registering form elements (inputs, selects, radios, etc) in the form. In order to do that you need to invoke the function itself and spread the props into the input. For example:
```jsx
-
+
```
-Register accepts an object which describes validation rules for a given input:
+The first argument for `register` is the field name. It also accepts an object, which describes validation rules for a given input:
```jsx
{ // custom validation rule }
@@ -70,7 +69,7 @@ See [Validation](#validation) for examples on validation and validation rules.
```jsx
-
+
```
@@ -89,22 +88,20 @@ import { Form, Field, InputControl } from '@grafana/ui';
}
{/* Pass control exposed from Form render prop */}
control={control}
name="radio"
- options={...}
/>
}
{/* Pass control exposed from Form render prop */}
control={control}
name="select"
- options={...}
/>
>
@@ -112,32 +109,30 @@ import { Form, Field, InputControl } from '@grafana/ui';
```
-Note that when using `InputControl`, it expects the name of the prop that handles input change to be called `onChange`.
-If the property is named differently for any specific component, additional `onChangeName` prop has to be provided, specifying the name.
-Additionally, the `onChange` arguments passed as an array. Check [react-hook-form docs](https://react-hook-form.com/api/#Controller)
-for more prop options.
+In case we want to modify the selected value before passing it to the form, we can use the `onChange` callback from the render's `field` argument:
```jsx
-{/* DashboardPicker has onSelected prop instead of onChange */}
-import { DashboardPicker } from 'app/core/components/Select/DashboardPicker';
-
-{/* In case of Select the value has to be returned as an object with a `value` key for the value to be saved to form data */}
-const onSelectChange = ([value]) => {
- // ...
- return { value };
-}
-
+```
+Note that `field` also contains `ref` prop, which is passed down to the rendered component by default. In case if that component doesn't support this prop, it will need to be removed before spreading the `field`.
+
+```jsx
+
+
```
### Default values
@@ -179,7 +174,7 @@ const defaultValues: FormDto {
@@ -197,9 +192,8 @@ Validation can be performed either synchronously or asynchronously. What's impor
<>
>
)}
@@ -217,8 +211,7 @@ One important thing to note is that if you want to provide different error messa
{
return v !== 'John' && 'Name must be John'
@@ -258,8 +251,7 @@ validateAsync = (newValue: string) => {
{
return await validateAsync(v);
@@ -271,6 +263,26 @@ validateAsync = (newValue: string) => {
```
+### Upgrading to v8
+Version 8 of Grafana-UI is using version 7 of `react-hook-form` (previously version 5 was used), which introduced a few breaking changes to the `Form` API. The detailed list of changes can be found in the library's migration guides:
+- [Migration guide v5 to v6](https://react-hook-form.com/migrate-v5-to-v6/)
+- [Migration guide v6 to v7](https://react-hook-form.com/migrate-v6-to-v7/)
+
+In a nutshell, the two most important changes are:
+- register method is no longer passed as a `ref`, but instead its result is spread onto the input component:
+```jsx
+-
++
+```
+- `InputControl`'s `as` prop has been replaced with `render`, which has `field` and `fieldState` objects as arguments. `onChange`, `onBlur`, `value`, `name`, and `ref` are parts of `field`.
+```jsx
+- } />
++ }
+// or
++ } />
+```
+
+
### Props
diff --git a/packages/grafana-ui/src/components/Forms/Form.story.tsx b/packages/grafana-ui/src/components/Forms/Form.story.tsx
index ddd12686352..42455b78057 100644
--- a/packages/grafana-ui/src/components/Forms/Form.story.tsx
+++ b/packages/grafana-ui/src/components/Forms/Form.story.tsx
@@ -1,8 +1,4 @@
import React from 'react';
-
-import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
-import { withStoryContainer } from '../../utils/storybook/withStoryContainer';
-import mdx from './Form.mdx';
import { ValidateResult } from 'react-hook-form';
import { Story } from '@storybook/react';
import {
@@ -18,9 +14,12 @@ import {
TextArea,
RadioButtonGroup,
} from '@grafana/ui';
+import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
+import { withStoryContainer } from '../../utils/storybook/withStoryContainer';
+import mdx from './Form.mdx';
export default {
- title: 'Forms/Example forms',
+ title: 'Forms/Form',
decorators: [withStoryContainer, withCenteredStory],
parameters: {
docs: {
@@ -48,20 +47,20 @@ const selectOptions = [
];
interface FormDTO {
- name: string;
- email: string;
- username: string;
- checkbox: boolean;
+ name?: string;
+ email?: string;
+ username?: string;
+ checkbox?: boolean;
switch: boolean;
radio: string;
select: string;
- text: string;
+ text?: string;
nested: {
path: string;
};
}
-const renderForm = (defaultValues?: Partial) => (
+const renderForm = (defaultValues?: FormDTO) => (
diff --git a/public/app/core/components/Login/LoginForm.tsx b/public/app/core/components/Login/LoginForm.tsx
index 6704c44e7a1..2a840e0cdf5 100644
--- a/public/app/core/components/Login/LoginForm.tsx
+++ b/public/app/core/components/Login/LoginForm.tsx
@@ -31,20 +31,18 @@ export const LoginForm: FC = ({ children, onSubmit, isLoggingIn, password
<>
diff --git a/public/app/core/components/Signup/SignupPage.tsx b/public/app/core/components/Signup/SignupPage.tsx
index bc3f2368db7..08de16051c1 100644
--- a/public/app/core/components/Signup/SignupPage.tsx
+++ b/public/app/core/components/Signup/SignupPage.tsx
@@ -63,50 +63,47 @@ export const SignupPage: FC = (props) => {
{({ errors, register, getValues }) => (
<>
-
+
{!getConfig().autoAssignOrg && (
-
+
)}
{getConfig().verifyEmailEnabled && (
-
+
)}
v === getValues().password || 'Passwords must match!',
})}
+ type="password"
/>
diff --git a/public/app/core/components/Signup/VerifyEmail.tsx b/public/app/core/components/Signup/VerifyEmail.tsx
index 463d2e294c4..053936f51b7 100644
--- a/public/app/core/components/Signup/VerifyEmail.tsx
+++ b/public/app/core/components/Signup/VerifyEmail.tsx
@@ -47,7 +47,7 @@ export const VerifyEmail: FC = () => {
invalid={!!(errors as any).email}
error={(errors as any).email?.message}
>
-
+
diff --git a/public/app/features/admin/AdminEditOrgPage.tsx b/public/app/features/admin/AdminEditOrgPage.tsx
index 03e82883df5..87b3dd13e59 100644
--- a/public/app/features/admin/AdminEditOrgPage.tsx
+++ b/public/app/features/admin/AdminEditOrgPage.tsx
@@ -66,7 +66,7 @@ export const AdminEditOrgPage: FC = ({ match }) => {
{({ register, errors }) => (
<>
-
+
>
diff --git a/public/app/features/admin/UserCreatePage.tsx b/public/app/features/admin/UserCreatePage.tsx
index 3c41acf2a82..1f6a05db135 100644
--- a/public/app/features/admin/UserCreatePage.tsx
+++ b/public/app/features/admin/UserCreatePage.tsx
@@ -46,15 +46,15 @@ const UserCreatePage: React.FC = ({ navModel }) => {
invalid={!!errors.name}
error={errors.name ? 'Name is required' : undefined}
>
-
+
-
+
-
+
= ({ navModel }) => {
error={errors.password ? 'Password is required and must contain at least 4 characters' : undefined}
>
value.trim() !== '' && value.length >= 4,
})}
+ type="password"
/>
diff --git a/public/app/features/alerting/components/BasicSettings.tsx b/public/app/features/alerting/components/BasicSettings.tsx
index 627849e1e28..1b996d3c6af 100644
--- a/public/app/features/alerting/components/BasicSettings.tsx
+++ b/public/app/features/alerting/components/BasicSettings.tsx
@@ -25,10 +25,15 @@ export const BasicSettings: FC = ({
return (
<>
-
+
-
+ }
+ control={control}
+ rules={{ required: true }}
+ />
o.required)}
diff --git a/public/app/features/alerting/components/NotificationChannelForm.tsx b/public/app/features/alerting/components/NotificationChannelForm.tsx
index da1ade32641..14b9fe9979f 100644
--- a/public/app/features/alerting/components/NotificationChannelForm.tsx
+++ b/public/app/features/alerting/components/NotificationChannelForm.tsx
@@ -9,7 +9,7 @@ import { ChannelSettings } from './ChannelSettings';
import config from 'app/core/config';
-interface Props extends Omit, 'formState'> {
+interface Props extends Omit, 'formState' | 'setValue'> {
selectableChannels: Array>;
selectedChannel?: NotificationChannelType;
imageRendererAvailable: boolean;
@@ -19,7 +19,7 @@ interface Props extends Omit, 'formState'> {
}
export interface NotificationSettingsProps
- extends Omit, 'formState' | 'watch' | 'getValues'> {
+ extends Omit, 'formState' | 'watch' | 'getValues' | 'setValue'> {
currentFormValues: NotificationChannelDTO;
}
@@ -100,7 +100,7 @@ export const NotificationChannelForm: FC = ({