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

Feat: 3960. Update setFieldValue with setter function #3968

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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(getIn(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