Skip to content

Commit

Permalink
[Form lib] Add "updateFieldValues" to the form hook API (#130544)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebelga authored Apr 20, 2022
1 parent b658a4a commit 36a4c99
Show file tree
Hide file tree
Showing 10 changed files with 563 additions and 54 deletions.
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]);
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

0 comments on commit 36a4c99

Please sign in to comment.