diff --git a/.changeset/afraid-buses-kiss.md b/.changeset/afraid-buses-kiss.md new file mode 100644 index 000000000..3e07d93d9 --- /dev/null +++ b/.changeset/afraid-buses-kiss.md @@ -0,0 +1,5 @@ +--- +'formik': patch +--- + +Add `useSetFieldValue` and `useSetFieldTouched` hooks diff --git a/.changeset/gentle-boxes-turn.md b/.changeset/gentle-boxes-turn.md new file mode 100644 index 000000000..f1ed355b7 --- /dev/null +++ b/.changeset/gentle-boxes-turn.md @@ -0,0 +1,15 @@ +--- +'formik': patch +--- + +Added optimized Form, ErrorMessage, and the following additional optimized field-level hooks +and components that only rerender when their respective slices have changed: + +- `unstable_useFieldValue` +- `unstable_useFieldTouched` +- `unstable_useFieldError` +- `unstable_useStrictField` +- `unstable_StrictField` + +In addition, there is a very very unstable `unstable_useFormikContextSelector` that we'll be using +to prototype other APIss in product that is also included. This will definitely **not** be released, so you have been warned. diff --git a/.changeset/honest-radios-poke.md b/.changeset/honest-radios-poke.md new file mode 100644 index 000000000..2c6e262db --- /dev/null +++ b/.changeset/honest-radios-poke.md @@ -0,0 +1,5 @@ +--- +'formik': patch +--- + +Fix useFieldTouched selector diff --git a/.changeset/lazy-olives-agree.md b/.changeset/lazy-olives-agree.md new file mode 100644 index 000000000..a0601cd8b --- /dev/null +++ b/.changeset/lazy-olives-agree.md @@ -0,0 +1,5 @@ +--- +'formik': patch +--- + +Add hooks `useSetValue`, `useSetErrors`, `useSetTouched`, `useSetStatus`, `useSubmitForm`, `useResetForm`, `useIsSubmitting`, `useIsValid`, `useIsDirty`, `useValidateForm`, `useValidateField` diff --git a/.changeset/little-olives-enjoy.md b/.changeset/little-olives-enjoy.md new file mode 100644 index 000000000..359a3ba39 --- /dev/null +++ b/.changeset/little-olives-enjoy.md @@ -0,0 +1,5 @@ +--- +'formik': patch +--- + +Ensure that parse and format aren't passed through in Field components diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 000000000..68a49c063 --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,19 @@ +{ + "mode": "pre", + "tag": "next", + "initialVersions": { + "formik": "2.2.1", + "formik-native": "2.1.9", + "@formik/codemod": "3.0.0-next.2" + }, + "changesets": [ + "afraid-buses-kiss", + "gentle-boxes-turn", + "honest-radios-poke", + "lazy-olives-agree", + "little-olives-enjoy", + "quiet-beans-attack", + "two-fans-film", + "wicked-goats-burn" + ] +} diff --git a/.changeset/quiet-beans-attack.md b/.changeset/quiet-beans-attack.md new file mode 100644 index 000000000..00169e094 --- /dev/null +++ b/.changeset/quiet-beans-attack.md @@ -0,0 +1,8 @@ +--- +'formik': major +--- + +Added `parse`, `format`, and `formatOnBlur` to `getFieldProps` options, ``, and `useField`. Going forward, there is no reason aside from backwards compatibility to continue using either `formikProps.handleChange` or `formikProps.handleBlur`. These are both inferior to the `onChange` and `onBlur` functions returned by `getFieldProps()` which the ability to utilize `parse`, `format`, and `formatOnBlur`. + +**Breaking Change** +Instead of just passing back `formikProps.handleChange` and `formikProps.handleBlur`, the `onChange` and `onBlur` handlers returned by `getFieldProps()` (and thus `useField`/``) are now scoped to the field already and now accept either a React Synthetic event or a value. In the past, you could need to curry the handler with the string name of field to get this functionality. This likely doesn't impact many users, but it is technically breaking nonetheless. diff --git a/.changeset/two-fans-film.md b/.changeset/two-fans-film.md new file mode 100644 index 000000000..e7b514893 --- /dev/null +++ b/.changeset/two-fans-film.md @@ -0,0 +1,19 @@ +--- +'formik': patch +--- + +Renames `unstable_StrictField` to `FastField` and thus deprecates `` prop. If you need this functionality, use `useFormikContext()` and `useField()` in a custom component wrapped in `React.memo()` instead. In addition, and this is breaking, `FastField` is no longer passed `form` object in any render prop. + +If you still need to access the `form` object in render use `FormikConsumer` like so: + +```diff +- import { FastField } from 'formik' ++ import { FastField, FormikConsumer } from 'formik' + + +- {({ field, meta, form }) => ( /* ... */ )} ++ {({ field, meta }) => ( ++ {form => /* ... */} ++ )} + +``` diff --git a/.changeset/wicked-goats-burn.md b/.changeset/wicked-goats-burn.md new file mode 100644 index 000000000..7a330cb38 --- /dev/null +++ b/.changeset/wicked-goats-burn.md @@ -0,0 +1,5 @@ +--- +'formik': patch +--- + +Implement useField field.onBlur to avoid rerendering entire form on blur events diff --git a/MIGRATING-v3.md b/MIGRATING-v3.md new file mode 100644 index 000000000..a2fec10a9 --- /dev/null +++ b/MIGRATING-v3.md @@ -0,0 +1,47 @@ +# Formik 3 Migration Guide + +## Breaking changes + +- `FastField` no longer accepts `shouldUpdate` prop. Given the new implementation of `FastField`, this functionality is likely no longer needed anyways. +- `FastField` is no longer passed a `form` object (which contained `formikProps` in any render prop. If you still need to access the `form` object in the render prop use regular `Field` (which updates on every render). +- Instead of just passing back `formikProps.handleChange` and `formikProps.handleBlur`, the `onChange` and `onBlur` handlers returned by `getFieldProps()` and `useField`/``/``) are now scoped to the field already and now accept either a React Synthetic event or a value. In the past, you could curry the handler with the string name of field to get this functionality. This likely doesn't impact many users, but it is technically breaking nonetheless. + +## Improvements + +- Selective rendering! +- `
`, ``, and `` have been re-implemented to optimize rendering (i.e. minimally re-render) +- `useField()` no longer updates on every key stroke, but only when that field is updated. This is a massive performance boost. +- Added `parse`, `format`, and `formatOnBlur` to `getFieldProps` options, ``, and `useField`. Going forward, there is no reason aside from backwards compatibility to continue using either `formikProps.handleChange` or `formikProps.handleBlur`. These are both inferior to the `onChange` and `onBlur` functions returned by `getFieldProps()`, ``/``'s `field` object, or `useField` which have the ability to utilize `parse`, `format`, and `formatOnBlur`. +- **New Hooks!** + - `useFieldInitialValue` + - `useFieldInitialTouched` + - `useFieldInitialError` + - `useInitialValues` + - `useInitialErrors` + - `useInitialTouched` + - `useInitialStatus` + - `useFieldTouched` + - `useFieldValue` + - `useFieldError` + - `useValues` + - `useSetValue` + - `useSetErrors` + - `useErrors` + - `useTouched` + - `useSetTouched` + - `useStatus` + - `useSetStatus` + - `useSubmitForm` + - `useResetForm` + - `useIsSubmitting` + - `useIsValid` + - `useIsDirty` + - `useValidateForm` + - `useValidateField` + +## Performance Suggestions + +- Prefer `` to `` +- Prefer a custom component with `useField()` over `` +- The new helpers in `field.onChange` and `field.onBlur` returned from `useField()` can now handle either events or raw values. There is thus no need to call `useFormikContext` just to access `setFieldValue` and `setFieldBlur` anymore if you're updating the same field. +- Move away from using `useFormikContext` and instead use one or more of the above hooks (ideally only the `useFieldXXX` ones if you can). `useFormikContext`, `useValues`, `useTouched` and `useErrors` hooks will rerender any components that use them when they change. Unless you're making a debugger, in most cases, your field or component doesn't need all of Formik state and so instead, use the new `useFieldXXX` hooks which will only rerender if that particular slice of Formik state updates. diff --git a/app/package.json b/app/package.json index d29ce15bb..0178a41c2 100644 --- a/app/package.json +++ b/app/package.json @@ -8,10 +8,16 @@ "start": "next start" }, "dependencies": { + "format-string-by-pattern": "^1.2.1", "formik": "^2.1.5", "next": "9.5.3", "react": "16.13.1", "react-dom": "16.13.1", "yup": "^0.29.3" + }, + "devDependencies": { + "@types/node": "^14.14.6", + "@types/react": "^16.9.56", + "@types/react-dom": "^16.9.9" } } diff --git a/app/pages/format.tsx b/app/pages/format.tsx new file mode 100644 index 000000000..35f42e3f4 --- /dev/null +++ b/app/pages/format.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { Formik, Field, Form } from 'formik'; +import formatString from 'format-string-by-pattern'; + +const masks = [ + { name: 'phone-1', parse: '999-999-9999' }, + { name: 'phone-2', parse: '(999) 999-9999' }, + { name: 'phone-3', parse: '+49 (AAAA) BBBBBB' }, +]; + +const sleep = (ms: number) => new Promise(r => setTimeout(r, ms)); + +export default function Example() { + return ( +
+ { + prev[curr.name] = ''; + return prev; + }, {} as any)} + onSubmit={async values => { + await sleep(300); + alert(JSON.stringify(values, null, 2)); + }} + > + + {masks.map(mask => ( +
+ +
+ ))} + + + +
+
+ ); +} diff --git a/app/pages/index.tsx b/app/pages/index.tsx index 30daffeed..cffed6a9f 100644 --- a/app/pages/index.tsx +++ b/app/pages/index.tsx @@ -16,6 +16,11 @@ function Home() { Async Submission +
  • + + Parse + Format + +