Skip to content

Commit 99dbc24

Browse files
authored
Feat: 3960. Update setFieldValue with setter function (#3968)
Feature for: #3960
1 parent 0e617db commit 99dbc24

4 files changed

Lines changed: 60 additions & 4 deletions

File tree

.changeset/spotty-poets-bathe.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'formik': patch
3+
---
4+
5+
Value of setFieldValue can be a function that takes previous field value

docs/api/formik.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ Trigger a form submission. The promise will be rejected if form is invalid.
193193
Number of times user tried to submit the form. Increases when [`handleSubmit`](#handlesubmit-e-reactformeventhtmlformelement--void) is called, resets after calling
194194
[`handleReset`](#handlereset---void). `submitCount` is readonly computed property and should not be mutated directly.
195195

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

198198
Set the value of a field imperatively. `field` should match the key of
199199
`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`.

packages/formik/src/Formik.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -582,18 +582,20 @@ export function useFormik<Values extends FormikValues = FormikValues>({
582582
);
583583

584584
const setFieldValue = useEventCallback(
585-
(field: string, value: any, shouldValidate?: boolean) => {
585+
(field: string, value: React.SetStateAction<any>, shouldValidate?: boolean) => {
586+
const resolvedValue = isFunction(value) ? value(getIn(state.values, field)) : value;
587+
586588
dispatch({
587589
type: 'SET_FIELD_VALUE',
588590
payload: {
589591
field,
590-
value,
592+
value: resolvedValue,
591593
},
592594
});
593595
const willValidate =
594596
shouldValidate === undefined ? validateOnChange : shouldValidate;
595597
return willValidate
596-
? validateFormWithHighPriority(setIn(state.values, field, value))
598+
? validateFormWithHighPriority(setIn(state.values, field, resolvedValue))
597599
: Promise.resolve();
598600
}
599601
);

packages/formik/test/Formik.test.tsx

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,55 @@ describe('<Formik>', () => {
740740
});
741741
});
742742

743+
it('setFieldValue sets value by key when takes a setter function', async () => {
744+
const { getProps, rerender } = renderFormik<Values>();
745+
746+
act(() => {
747+
getProps().setFieldValue('name', (prev: string) => {
748+
return prev + ' chronicus';
749+
});
750+
});
751+
rerender();
752+
await waitFor(() => {
753+
expect(getProps().values.name).toEqual('jared chronicus');
754+
});
755+
});
756+
757+
it(
758+
'setFieldValue should run validations with resolved value when takes a setter function and validateOnChange is true (default)',
759+
async () => {
760+
const validate = jest.fn(() =>({}));
761+
const { getProps, rerender } = renderFormik({ validate });
762+
763+
act(() => {
764+
getProps().setFieldValue('name', (prev: string) => prev + ' chronicus');
765+
});
766+
rerender();
767+
await waitFor(() => {
768+
// the validate function is called with the second arg as undefined always in this case
769+
expect(validate).toHaveBeenCalledWith(expect.objectContaining({
770+
name: 'jared chronicus',
771+
}), undefined);
772+
});
773+
}
774+
);
775+
776+
it('setFieldValue should NOT run validations when takes a setter function and validateOnChange is false', async () => {
777+
const validate = jest.fn();
778+
const { getProps, rerender } = renderFormik({
779+
validate,
780+
validateOnChange: false,
781+
});
782+
783+
act(() => {
784+
getProps().setFieldValue('name', (prev: string) => prev + ' chronicus');
785+
});
786+
rerender();
787+
await waitFor(() => {
788+
expect(validate).not.toHaveBeenCalled();
789+
});
790+
});
791+
743792
it('setTouched sets touched', () => {
744793
const { getProps } = renderFormik();
745794

0 commit comments

Comments
 (0)