Skip to content

Commit

Permalink
Extend setValue to use React.SetStateAction functionality (#2556)
Browse files Browse the repository at this point in the history
This is a continuation of #1513 which was closed for inactivity. This PR has addressed all the comments and reused React.SetStateAction types.

With this, formik consumers could pass a value or a function to setValues just like you can with React's setState dispatchers.
  • Loading branch information
PatoBeltran authored Oct 1, 2020
1 parent 83c0299 commit ad1d008
Show file tree
Hide file tree
Showing 5 changed files with 36 additions and 16 deletions.
10 changes: 5 additions & 5 deletions docs/src/pages/docs/2.1.4/api/formik.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Each render methods will be passed the same props:
Returns `true` if values are not deeply equal from initial values, `false` otherwise.
`dirty` is a readonly computed property and should not be mutated directly.

#### `errors: { [field: string]: string }`
#### `errors: FormikErrors<Values>`

Form validation errors. Should match the shape of your form's `values` defined
in `initialValues`. If you are using `validationSchema` (which you should be),
Expand Down Expand Up @@ -114,7 +114,7 @@ Returns `true` if Formik is running validation during submission, or by calling

Imperatively reset the form. If `nextInitialState` is specified, Formik will set this state as the new "initial state" and use the related values of `nextInitialState` to update the form's `initialValues` as well as `initialTouched`, `initialStatus`, `initialErrors`. This is useful for altering the initial state (i.e. "base") of the form after changes have been made. If `nextInitialState` is not defined, then Formik will reset state to the original initial state. The latter is useful for calling `resetForm` within `componentDidUpdate` or `useEffect`.

#### `setErrors: (fields: { [field: string]: string }) => void`
#### `setErrors: (fields: FormikErrors<Values>) => void`

Set `errors` imperatively.

Expand Down Expand Up @@ -156,7 +156,7 @@ Set `isSubmitting` imperatively. You would call it with `setSubmitting(false)` i

Set `touched` imperatively. Calling this will trigger validation to run if `validateOnBlur` is set to `true` (which it is by default). You can also explicitly prevent/skip validation by passing a second argument as `false`.

#### `setValues: (fields: { [field: string]: any }, shouldValidate?: boolean) => void`
#### `setValues: (fields: React.SetStateAction<Values>, shouldValidate?: boolean) => void`

Set `values` imperatively. 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 second argument as `false`.

Expand All @@ -169,11 +169,11 @@ and passing through API responses to your inner component.
`status` should only be modified by calling
[`setStatus`](#setstatus-status-any-void).

#### `touched: { [field: string]: boolean }`
#### `touched: FormikTouched<Values>`

Touched fields. Each key corresponds to a field that has been touched/visited.

#### `values: { [field: string]: any }`
#### `values: Values`

Your form's values. Will have the shape of the result of `mapPropsToValues`
(if specified) or all props that are not functions passed to your wrapped
Expand Down
2 changes: 1 addition & 1 deletion docs/src/pages/docs/2.1.4/guides/validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ export const FieldLevelValidationExample = () => (

You can control when Formik runs validation by changing the values of `<Formik validateOnChange>` and/or `<Formik validateOnBlur>` props depending on your needs. By default, Formik will run validation methods as follows:

**After "change" events/methods** (things that update`values`)
**After "change" events/methods** (things that update `values`)

- `handleChange`
- `setFieldValue`
Expand Down
10 changes: 7 additions & 3 deletions packages/formik/src/Formik.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -581,12 +581,16 @@ export function useFormik<Values extends FormikValues = FormikValues>({
}, []);

const setValues = useEventCallback(
(values: Values, shouldValidate?: boolean) => {
dispatch({ type: 'SET_VALUES', payload: values });
(values: React.SetStateAction<Values>, shouldValidate?: boolean) => {
const resolvedValues = isFunction(values)
? values(state.values)
: values;

dispatch({ type: 'SET_VALUES', payload: resolvedValues });
const willValidate =
shouldValidate === undefined ? validateOnChange : shouldValidate;
return willValidate
? validateFormWithLowPriority(values)
? validateFormWithLowPriority(resolvedValues)
: Promise.resolve();
}
);
Expand Down
2 changes: 1 addition & 1 deletion packages/formik/src/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export interface FormikHelpers<Values> {
shouldValidate?: boolean
) => void;
/** Manually set values object */
setValues: (values: Values, shouldValidate?: boolean) => void;
setValues: (values: React.SetStateAction<Values>, shouldValidate?: boolean) => void;
/** Set value of form field directly */
setFieldValue: (field: string, value: any, shouldValidate?: boolean) => void;
/** Set error message of a form field directly */
Expand Down
28 changes: 22 additions & 6 deletions packages/formik/test/Formik.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ jest.spyOn(global.console, 'warn');

interface Values {
name: string;
age?: number;
}

function Form({
Expand Down Expand Up @@ -48,10 +49,13 @@ function Form({
);
}

const InitialValues = { name: 'jared' };
const InitialValues = {
name: 'jared',
age: 30,
};

function renderFormik<V = Values>(props?: Partial<FormikConfig<V>>) {
let injected: any;
let injected: FormikProps<V>;
const { rerender, ...rest } = render(
<Formik
onSubmit={noop as any}
Expand Down Expand Up @@ -454,7 +458,7 @@ describe('<Formik>', () => {
expect(getProps().touched).toEqual({});

fireEvent.submit(getByTestId('form'));
expect(getProps().touched).toEqual({ name: true });
expect(getProps().touched).toEqual({ name: true, age: true });
});

it('should push submission state changes to child component', () => {
Expand Down Expand Up @@ -609,8 +613,20 @@ describe('<Formik>', () => {
it('setValues sets values', () => {
const { getProps } = renderFormik<Values>();

getProps().setValues({ name: 'ian' });
getProps().setValues({ name: 'ian', age: 25 });
expect(getProps().values.name).toEqual('ian');
expect(getProps().values.age).toEqual(25);
});

it('setValues takes a function which can patch values', () => {
const { getProps } = renderFormik<Values>();

getProps().setValues((values: Values) => ({
...values,
age: 80,
}));
expect(getProps().values.name).toEqual('jared');
expect(getProps().values.age).toEqual(80);
});

it('setValues should run validations when validateOnChange is true (default)', async () => {
Expand Down Expand Up @@ -765,7 +781,7 @@ describe('<Formik>', () => {
const { getProps } = renderFormik();

expect(getProps().dirty).toBeFalsy();
getProps().setValues({ name: 'ian' });
getProps().setValues({ name: 'ian', age: 27 });
expect(getProps().dirty).toBeTruthy();
});

Expand Down Expand Up @@ -853,7 +869,7 @@ describe('<Formik>', () => {
getProps().resetForm();

expect(onReset).toHaveBeenCalledWith(
{ name: 'jared' },
InitialValues,
expect.objectContaining({
resetForm: expect.any(Function),
setErrors: expect.any(Function),
Expand Down

0 comments on commit ad1d008

Please sign in to comment.