Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions src/plugins/es_ui_shared/static/forms/docs/core/default_value.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
---
id: formLibCoreDefaultValue
slug: /form-lib/core/default-value
title: Default value
summary: Initiate a field with the correct value
tags: ['forms', 'kibana', 'dev']
date: 2021-04-14
---

There are multiple places where you can define the default value of a field. By "default value" we are saying "the initial value" of a field. Once the field is initiated it has its own internal state and can't be controlled.

## Order of precedence

1. As a prop on the `<UseField path="name" defaultValue="John" />` component
2. In the **form** `defaultValue` config passed to `useForm({ defaultValue: { ... } })`
3. In the **field** `defaultValue` config parameter (either passed as prop to `<UseField />` prop or declared inside a form schema)
4. If no default value is found above, it defaults to `""` (empty string)

### As a prop on `<UseField />`

This takes over any other `defaultValue` defined elsewhere. What you provide as prop is what you will have as default value for the field. Remember that the `<UseField />` **is not** a controlled component, so changing the `defaultValue` prop to another value does not have any effect.

```js
// Here we manually set the default value
<UseField path="user.firstName" defaultValue="John" />
```

### In the form `defaultValue` config passed to `useForm()`

The above solution works well for very small forms, but with larger form it is not very convenient to manually add the default value of each field.

```js
// Let's imagine some data coming from the server
const fetchedData = {
user: {
firstName: 'John',
lastName: 'Snow',
}
}

// We need to manually write each connection, which is not convenient
<UseField path="user.firstName" defaultValue={fetchedData.user.firstName} />
<UseField path="user.lastName" defaultValue={fetchedData.user.lastName} />
```

It is much easier to provide the `defaultValue` object (probably some data that we have fetched from the server) at the form level

```js
const { form } = useForm({ defaultValue: fetchedData });

// And the defaultValue for each field will be automatically mapped to its paths
<UseField path="user.firstName" />
<UseField path="user.lastName" />
```

### In the field `defaultValue` config parameter of the field config

When you are creating a new resource, the form is empty and there is no data coming from the server to map. You still might want to define a default value for your fields.

```js
interface Props {
fetchedData?: { foo: boolean }
}

export const MyForm = ({ fetchedData }: Props) => {
// fetchedData can be "undefined" or an object.
// If it is undefined, then the config.defaultValue will be used
const { form } = useForm({ defaultValue: fetchedData });

return (
<UseField path="foo" config={{ defaultValue: true } />
);
}
```

Or the same but using a form schema

```js
const schema = {
// Field config for the path "foo" declared below
foo: {
defaultValue: true,
},
};

export const MyComponent = ({ fetchedData }: Props) => {
// 1. If "fetchedData" is not undefined **and** there is a value at the "foo" path, use it
// 2. otherwise, if there is a schema with a config at the "foo" path, read its "defaultValue"
// 3. otherwise use an "" (empty string)
const { form } = useForm({ schema, defaultValue: fetchedData });

return (
<UseField path="foo" />
);
}
```
188 changes: 188 additions & 0 deletions src/plugins/es_ui_shared/static/forms/docs/core/field_hook.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
---
id: formLibCoreFieldHook
slug: /form-lib/core/field-hook
title: Field hook
summary: You don't manually create them but you'll get all the love from them
tags: ['forms', 'kibana', 'dev']
date: 2021-04-14
---

When you use the `<UseField />` component you receive back a `field` hook object that you can connect to your React components.

This hook has the following properties and handlers:

## Properties

### path

**Type:** `string`

The field `path`.

### label

**Type:** `string`

The field `label` provided in the config.

### labelAppend

**Type:** `string | ReactNode`

The field `labelAppend` provided in the config.

### helpText

**Type:** `string | ReactNode`

The field `helpText` provided in the config.

### type

**Type:** `string`

The field `type` provided in the config.

### value

**Type:** `T`

The field state value.

### errors

**Type:** `ValidationError[]`

An array of possible validation errors. Each error has a required `message` property and any other meta data returned by your validation(s).

### isValid

**Type:** `boolean`

Flag that indicates if the field is valid.

### isPristine

**Type:** `boolean`

Flag that indicates if the field is pristine (if it hasn't been modified by the user).

### isValidating

**Type:** `boolean`

Flag that indicates if the field is being validated. It is set to `true` when the value changes, and back to `false` right after all the validations have executed. If all your validations are synchronous, this state is always `false`.

### isValidated

**Type:** `boolean`

Flag that indicates if this field has run at least once its validation(s). The validations are run when the field values changes or, if the field value has not changed, when we call `form.submit()` or `form.validate()`.

### isChangingValue

**Type:** `boolean`

Flag that indicates if the field value is changing. If you have set the [`valueChangeDebounceTime`](use_field.md#valuechangedebouncetime) to `0`, then this state is the same as the `isValidating` state. But if you have increased the `valueChangeDebounceTime` time, then you will have a minimum value changing time. This is useful if you want to display your validation errors after a certain amount of time has passed.

## Handlers

### setValue()

**Arguments:** `value: T | (prevValue: T) => T`
**Returns:** `void`

Handler to set the value of the field.
You can either pass the value directly or provide a callback that will receive the previous field value and you will have to return the next value.

### onChange()

**Arguments:** `event: React.ChangeEvent<HTMLInputElement>`
**Returns:** `void`

Use the `onChange` helper to directly hook into the forms fields inputs `onChange` prop without having to extract the event value and call `setValue()` on the field.

```js
// Instead of this
<UseField path="name">
{({ setValue }) => {
return <input type="text" value={field.value} onChange={(e) => setValue(e.target.value)} />
}}
</UseField>

// You can use the "onChange" handler
<UseField path="name">
{({ onChange }) => {
return <input type="text" value={field.value} onChange={onChange} />
}}
</UseField>
```

### setErrors()

**Arguments:** `ValidationError[]`
**Returns:** `void`

Handler to set the errors of the field.

### clearErrors()

**Arguments:** `type?: string | string[]`
**Returns:** `void`

Handler to clear the errors of the field. You can optionally provide the type of error to clear.
See an example of typed validation when <DocLink id="formLibExampleValidation" section="validating-arrays-of-items" text="validating an array of items" />.

### getErrorsMessages()

**Arguments:** `options?: { validationType?: string; errorCode?: string }`
**Returns:** `string | null`

Returns a concatenated string with all the error messages if the field has errors, or `null` otherwise.

You can optionally provide an error code or a validation type to narrow down the errors you want to receive back.

**Note:** You can add error code to your errors by adding a `code` property to your validation errors.

```js
const nameValidator = ({ value }) => {
if (value.startsWith('.')) => {
return {
message: "The name can't start with a dot (.)",
code: 'ERR_NAME_FORMAT',
};
}
};
```

### validate()

**Arguments:** `options?: { formData?: any; value?: T; validationType?: string; }`
**Returns:** `FieldValidateResponse | Promise<FieldValidateResponse>`

Validate the field by calling all the validations declared in its config. Optionally you can provide an options object with the following properties:

* `formData` - The form data
* `value` - The value to validate
* `validationType` - The validation type to run against the value

You rarely need to manually call this method as it is automatically done for you whenever the field value changes.

**Important:** Calling `validate()` **does not update** the form `isValid` state and is only meant to get the field validity at a point in time.

#### Example where you might need this method:

The user changes the value inside one of your components and you receive this value in an `onChange` handler. Before updating the field value with `setValue()`, you want to validate this value and maybe prevent the field `value` to be updated at all.

### reset()

**Arguments:** `options?: { resetValue?: boolean; defaultValue?: T }`
**Returns:** `T | undefined`

Resets the field to its initial state. It accepts an optional configuration object:

- `resetValue` (default: `true`). Flag to indicate if we want to not only reset the field state (`errors`, `isPristine`...) but also the field value. If set to `true`, it will put back the default value passed to the field, or to the form, or declared on the field config (in that order).

- `defaultValue`. In some cases you might not want to reset the field to the default value initiallly provided. In this case you can provide a new `defaultValue` value when resetting.

If you provided a new `defaultValue`, you will receive back this value after it has gone through any possible `deserializer(s)` defined for that field. If you didn't provide a default value `undefined` is returned.
79 changes: 79 additions & 0 deletions src/plugins/es_ui_shared/static/forms/docs/core/form_component.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
id: formLibCoreFormComponent
slug: /form-lib/core/form-component
title: <Form />
summary: The boundary of your form
tags: ['forms', 'kibana', 'dev']
date: 2021-04-14
---

Once you have created <DocLink id="formLibCoreFormHook" text="a form hook"/>, you can wrap your form with the `<Form />` component.

This component accepts the following props.

## Props

### form (required)

**Type:** `FormHook`

The form hook you've created with `useForm()`.

```js
const MyFormComponent = () => {
const { form } = useForm();

return (
<Form form={form}>
...
</Form>
);
};
```

### FormWrapper

**Type:** `React.ComponentType`
**Default:**: `EuiForm`

This is the component that will wrap your form fields. By default it renders the `<EuiForm />` component.

Any props that you pass to the `<Form />` component, except the `form` hook, will be forwarded to that component.

```js
const MyFormComponent = () => {
const { form } = useForm();

// "isInvalid" and "error" are 2 props from <EuiForm />
return (
<Form form={form} isInvalid={form.isSubmitted && !form.isValid} error={form.getErrors()}>
...
</Form>
);
};
```

By default, `<EuiForm />` wraps the form with a `<div>` element. In some cases semantic HTML is preferred: wrapping your form with the `<form>` element. This also allows the user to submit the form by hitting the "ENTER" key inside a field.

**Important:** Make sure to **not** declare the FormWrapper inline on the prop but outside of your component.

```js
// Create a wrapper component with the <form> element
const FormWrapper = (props: any) => <form {...props} />;

export const MyFormComponent = () => {
const { form } = useForm();

// Hitting the "ENTER" key in a textfield will submit the form.
const submitForm = async () => {
const { isValid, data } = await form.submit();
...
};

return (
<Form form={form} FormWrapper={FormWrapper} onSubmit={submitForm}>
...
</Form>
);
};
```
Loading