Skip to content

Commit

Permalink
Feat: 3960. Add setter function for setFieldValue
Browse files Browse the repository at this point in the history
  • Loading branch information
ChronicusUA committed Apr 25, 2024
1 parent c798145 commit d7af688
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/spotty-poets-bathe.md
@@ -0,0 +1,5 @@
---
'formik': patch
---

Value of setFieldValue can be a function that takes previous field value
2 changes: 1 addition & 1 deletion docs/api/formik.md
Expand Up @@ -193,7 +193,7 @@ Trigger a form submission. The promise will be rejected if form is invalid.
Number of times user tried to submit the form. Increases when [`handleSubmit`](#handlesubmit-e-reactformeventhtmlformelement--void) is called, resets after calling
[`handleReset`](#handlereset---void). `submitCount` is readonly computed property and should not be mutated directly.

#### `setFieldValue: (field: string, value: any, shouldValidate?: boolean) => Promise<void | FormikErrors>`
#### `setFieldValue: (field: string, value: React.SetStateAction<any>, shouldValidate?: boolean) => Promise<void | FormikErrors>`

Set the value of a field imperatively. `field` should match the key of
`values` you wish to update. Useful for creating custom input change handlers. Calling this will trigger validation to run if `validateOnChange` is set to `true` (which it is by default). You can also explicitly prevent/skip validation by passing a third argument as `false`.
Expand Down
8 changes: 5 additions & 3 deletions packages/formik/src/Formik.tsx
Expand Up @@ -582,18 +582,20 @@ export function useFormik<Values extends FormikValues = FormikValues>({
);

const setFieldValue = useEventCallback(
(field: string, value: any, shouldValidate?: boolean) => {
(field: string, value: React.SetStateAction<any>, shouldValidate?: boolean) => {
const resolvedValue = isFunction(value) ? value(state.values[field]) : value;

dispatch({
type: 'SET_FIELD_VALUE',
payload: {
field,
value,
value: resolvedValue,
},
});
const willValidate =
shouldValidate === undefined ? validateOnChange : shouldValidate;
return willValidate
? validateFormWithHighPriority(setIn(state.values, field, value))
? validateFormWithHighPriority(setIn(state.values, field, resolvedValue))
: Promise.resolve();
}
);
Expand Down
49 changes: 49 additions & 0 deletions packages/formik/test/Formik.test.tsx
Expand Up @@ -740,6 +740,55 @@ describe('<Formik>', () => {
});
});

it('setFieldValue sets value by key when takes a setter function', async () => {
const { getProps, rerender } = renderFormik<Values>();

act(() => {
getProps().setFieldValue('name', (prev: string) => {
return prev + ' chronicus';
});
});
rerender();
await waitFor(() => {
expect(getProps().values.name).toEqual('jared chronicus');
});
});

it(
'setFieldValue should run validations with resolved value when takes a setter function and validateOnChange is true (default)',
async () => {
const validate = jest.fn(() =>({}));
const { getProps, rerender } = renderFormik({ validate });

act(() => {
getProps().setFieldValue('name', (prev: string) => prev + ' chronicus');
});
rerender();
await waitFor(() => {
// the validate function is called with the second arg as undefined always in this case
expect(validate).toHaveBeenCalledWith(expect.objectContaining({
name: 'jared chronicus',
}), undefined);
});
}
);

it('setFieldValue should NOT run validations when takes a setter function and validateOnChange is false', async () => {
const validate = jest.fn();
const { getProps, rerender } = renderFormik({
validate,
validateOnChange: false,
});

act(() => {
getProps().setFieldValue('name', (prev: string) => prev + ' chronicus');
});
rerender();
await waitFor(() => {
expect(validate).not.toHaveBeenCalled();
});
});

it('setTouched sets touched', () => {
const { getProps } = renderFormik();

Expand Down

0 comments on commit d7af688

Please sign in to comment.