Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Form lib] Add "updateFieldValues" to the form hook API #130544

Merged
merged 2 commits into from
Apr 20, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ export const ComboBoxField = ({ field, euiFieldProps = {}, idAria, ...rest }: Pr

const onCreateComboOption = (value: string) => {
// Note: for now, all validations for a comboBox array item have to be synchronous
// If there is a need to support asynchronous validation, we'll work on it (and will need to update the <EuiComboBox /> logic).
// If there is a need to support asynchronous validation, we'll need to update this handler and
// make the <EuiComboBox /> "onCreateOption" handler async).
const { isValid } = field.validate({
value,
validationType: VALIDATION_TYPES.ARRAY_ITEM,
Expand Down Expand Up @@ -84,7 +85,7 @@ export const ComboBoxField = ({ field, euiFieldProps = {}, idAria, ...rest }: Pr
placeholder={i18n.translate('esUi.forms.comboBoxField.placeHolderText', {
defaultMessage: 'Type and then hit "ENTER"',
})}
selectedOptions={(field.value as any[]).map((v) => ({ label: v }))}
selectedOptions={(field.value as string[]).map((v) => ({ label: v }))}
onCreateOption={onCreateComboOption}
onChange={onComboChange}
onSearchChange={onSearchComboChange}
Expand Down
19 changes: 19 additions & 0 deletions src/plugins/es_ui_shared/static/forms/docs/core/form_hook.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,22 @@ Sets field errors imperatively.
```js
form.setFieldErrors('name', [{ message: 'There is an error in the field' }]);
```

### updateFieldValues()

**Arguments:** `updatedFormData: Partial<T>, options?: { runDeserializer?: boolean }`

Update multiple field values at once. You don't need to provide all the form fields, **partial** update is supported. This method is mainly useful to update an array of object fields or to avoid multiple `form.setFieldValue()` calls.

```js
// Update an array of object (e.g "myArray[0].foo", "myArray[0].baz"...)
form.updateFieldValues({
myArray: [
{ foo: 'bar', baz: true },
{ foo2: 'bar2', baz: false }
]
});

// or simply multiple fields at once
form.updateFieldValues({ foo: 'bar', baz: false })
```
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,7 @@ describe('<UseArray />', () => {
<>
{items.map(({ id, path }) => {
return (
<UseField
key={id}
path={`${path}.name`}
data-test-subj={`nameField__${id}`}
/>
<UseField key={id} path={`${path}.name`} data-test-subj={`${path}Name`} />
);
})}
</>
Expand All @@ -102,7 +98,7 @@ describe('<UseArray />', () => {
} = setup();

await act(async () => {
setInputValue('nameField__0', 'John');
setInputValue('users[0]Name', 'John');
});

const formData = onFormData.mock.calls[onFormData.mock.calls.length - 1][0];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
* Side Public License, v 1.
*/

import uuid from 'uuid';
import { useEffect, useRef, useCallback, useMemo } from 'react';

import { FormHook, FieldConfig } from '../types';
Expand Down Expand Up @@ -37,6 +36,25 @@ export interface FormArrayField {
form: FormHook;
}

let uniqueId = 0;

export const createArrayItem = (path: string, index: number, isNew = true): ArrayItem => ({
id: uniqueId++,
path: `${path}[${index}]`,
isNew,
});

/**
* We create an internal field to represent the Array items. This field is not returned
* as part as the form data but is used internally to run validation on the array items.
* It is this internal field value (ArrayItem[]) that we then map to actual form fields
* (in the children func <UseArray>{({ items }) => (...)}</UseArray>)
*
* @param path The array path in the form data
* @returns The internal array field path
*/
export const getInternalArrayFieldPath = (path: string): string => `${path}__array__`;

/**
* Use UseArray to dynamically add fields to your form.
*
Expand All @@ -60,41 +78,26 @@ export const UseArray = ({
children,
}: Props) => {
const isMounted = useRef(false);
const uniqueId = useRef(0);

const form = useFormContext();
const { getFieldDefaultValue } = form;

const getNewItemAtIndex = useCallback(
(index: number): ArrayItem => ({
id: uniqueId.current++,
path: `${path}[${index}]`,
isNew: true,
}),
[path]
);

const fieldDefaultValue = useMemo<ArrayItem[]>(() => {
const defaultValues = readDefaultValueOnForm
? getFieldDefaultValue<unknown[]>(path)
: undefined;

if (defaultValues) {
return defaultValues.map((_, index) => ({
id: uniqueId.current++,
path: `${path}[${index}]`,
isNew: false,
}));
return defaultValues.map((_, index) => createArrayItem(path, index, false));
}

return new Array(initialNumberOfItems).fill('').map((_, i) => getNewItemAtIndex(i));
}, [path, initialNumberOfItems, readDefaultValueOnForm, getFieldDefaultValue, getNewItemAtIndex]);
return new Array(initialNumberOfItems).fill('').map((_, i) => createArrayItem(path, i));
}, [path, initialNumberOfItems, readDefaultValueOnForm, getFieldDefaultValue]);

// Create an internal hook field which behaves like any other form field except that it is not
// outputed in the form data (when calling form.submit() or form.getFormData())
// This allow us to run custom validations (passed to the props) on the Array items

const internalFieldPath = useMemo(() => `${path}__${uuid.v4()}`, [path]);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need a predictable field name to be able to update the field values in updateFieldValues()

const internalFieldPath = useMemo(() => getInternalArrayFieldPath(path), [path]);

const fieldConfigBase: FieldConfig<ArrayItem[]> & InternalFieldConfig<ArrayItem[]> = {
defaultValue: fieldDefaultValue,
Expand Down Expand Up @@ -132,9 +135,9 @@ export const UseArray = ({
const addItem = useCallback(() => {
setValue((previousItems) => {
const itemIndex = previousItems.length;
return [...previousItems, getNewItemAtIndex(itemIndex)];
return [...previousItems, createArrayItem(path, itemIndex)];
});
}, [setValue, getNewItemAtIndex]);
}, [setValue, path]);

const removeItem = useCallback(
(id: number) => {
Expand Down
Loading