diff --git a/.changeset/flat-peaches-hug.md b/.changeset/flat-peaches-hug.md new file mode 100644 index 000000000..e830f2714 --- /dev/null +++ b/.changeset/flat-peaches-hug.md @@ -0,0 +1,6 @@ +--- +'formik': major +'app': minor +--- + +Add strongly typed fields in an opt-in fashion using useTypedField() and useTypedFieldArray(). diff --git a/.gitignore b/.gitignore index 77b73f52e..3cf8019bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ dist +dist-untyped compiled *.log coverage @@ -7,6 +8,7 @@ next.d.ts legacy.d.ts .idea *.orig +__generated__ node_modules package-lock.json diff --git a/.vscode/launch.json b/.vscode/launch.json index b03621422..e9c528654 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,12 +5,12 @@ "version": "0.2.0", "configurations": [ { - "name": "Run NPM Dev", + "name": "Run Dev Web App", "request": "launch", "type": "node", "runtimeArgs": [ "run", - "start:app" + "start:web" ], "runtimeExecutable": "npm", "cwd": "${workspaceFolder}", @@ -18,6 +18,27 @@ "/**" ] }, + { + "name": "Run Docs Website", + "request": "launch", + "type": "node", + "runtimeArgs": [ + "run", + "start:website" + ], + "runtimeExecutable": "npm", + "cwd": "${workspaceFolder}", + "skipFiles": [ + "/**" + ] + }, + { + "type": "chrome", + "request": "launch", + "name": "Launch Chrome against localhost", + "url": "http://localhost:3000", + "webRoot": "${workspaceFolder}/app" + }, { "type": "node", "name": "vscode-jest-tests", @@ -37,13 +58,6 @@ "protocol": "inspector", "internalConsoleOptions": "neverOpen", "disableOptimisticBPs": true - }, - { - "type": "chrome", - "request": "launch", - "name": "Launch Chrome against localhost", - "url": "http://localhost:3000", - "webRoot": "${workspaceFolder}/app" } ] } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 10e02695f..381d54fdb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,4 @@ { "typescript.tsdk": "node_modules/typescript/lib", - "jest.pathToJest": "npm run test:vscode --", + "jest.jestCommandLine": "npm run test:vscode --", } diff --git a/app-native/__generated__/AppEntry.js b/app-native/__generated__/AppEntry.js deleted file mode 100644 index 699b23c80..000000000 --- a/app-native/__generated__/AppEntry.js +++ /dev/null @@ -1,13 +0,0 @@ -// @generated by expo-yarn-workspaces - -import 'expo/build/Expo.fx'; -import { activateKeepAwake } from 'expo-keep-awake'; -import registerRootComponent from 'expo/build/launch/registerRootComponent'; - -import App from '../App'; - -if (__DEV__) { - activateKeepAwake(); -} - -registerRootComponent(App); diff --git a/app/pages/tutorial/field-array.tsx b/app/pages/tutorial/field-array.tsx deleted file mode 100644 index 80aef43ea..000000000 --- a/app/pages/tutorial/field-array.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import React from 'react'; -import { Formik, Form, Field, FieldArray } from 'formik'; - -// Here is an example of a form with an editable list. -// Next to each input are buttons for insert and remove. -// If the list is empty, there is a button to add an item. -export const FriendList = () => ( -
-

Friend List

- { - setTimeout(() => { - alert(JSON.stringify(values, null, 2)); - }, 500); - }} - render={({ values }) => ( -
- ( -
- {values.friends && values.friends.length > 0 ? ( - values.friends.map((_friend, index) => ( -
- - - -
- )) - ) : ( - - )} -
- -
-
- )} - /> - - )} - /> -
-); - -export default FriendList; diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 000000000..854c720dd --- /dev/null +++ b/docs/api.md @@ -0,0 +1,53 @@ +--- +id: api +title: The Formik API +--- + +### `handleBlur` + +> `(e: any) => void` + +`onBlur` event handler. Useful for when you need to track whether an input has +been `touched` or not. This should be passed to `` + +### `handleChange` + +> `(e: React.ChangeEvent) => void` + +General input change event handler. This will update the `values[key]` where +`key` is the event-emitting input's `name` attribute. If the `name` attribute is +not present, `handleChange` will look for an input's `id` attribute. Note: +"input" here means all HTML inputs. + +### `handleReset` + +> `() => void` + +Reset handler. Will reset the form to its initial state. This should be passed +to `` + +### `handleSubmit` + +> `(e: React.FormEvent) => void` + +Submit handler. This should be passed to `
...
`. To learn more about the submission process, see [Form Submission](../guides/form-submission.md). + +### `formik.useState()` + +> ```ts +> ( +> selector: (state: FormikState) => Return +> comparer: (prev: Return, next: Return) => boolean +> shouldSubscribe: boolean +> ) => Return; +> ``` + +A special hook returned by Formik's API to access Formik's State within a render outside of Formik's context. See more in the [deep dive](./deep-dive#formik-state). + +### `formik.getState()` + +> ```ts +> () => FormikState; +> ``` + +A method to access Formik's State outside of a render, for example in an event handler. See more in the [deep dive](./deep-dive#formik-state). diff --git a/docs/api/useFormikContext.md b/docs/api/useFormikContext.md deleted file mode 100644 index 11b415ff7..000000000 --- a/docs/api/useFormikContext.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -id: useFormikContext -title: useFormikContext() ---- - -`useFormikContext()` is a custom React hook that will return all Formik state and helpers via [React Context](https://reactjs.org/docs/context.html). - -## Example - -Here's an example of a form that works similarly to Stripe's 2-factor verification form. As soon as you type a 6 digit number, the form will automatically submit (i.e. no enter keypress is needed). - -```js -import React from 'react'; -import { useFormikContext, Formik, Form, Field } from 'formik'; - -const AutoSubmitToken = () => { - // Grab values and submitForm from context - const { values, submitForm } = useFormikContext(); - React.useEffect(() => { - // Submit the form imperatively as an effect as soon as form values.token are 6 digits long - if (values.token.length === 6) { - submitForm(); - } - }, [values, submitForm]); - return null; -}; - -const TwoFactorVerificationForm = () => ( -
-

2-step Verification

-

Please enter the 6 digit code sent to your device

- { - const errors = {}; - if (values.token.length < 5) { - errors.token = 'Invalid code. Too short.'; - } - return errors; - }} - onSubmit={(values, actions) => { - setTimeout(() => { - alert(JSON.stringify(values, null, 2)); - actions.setSubmitting(false); - }, 1000); - }} - > -
- - - -
-
-); -``` - ---- - -# Reference - -## `useFormikContext(): FormikProps` - -A custom React Hook that returns Formik states and helpers via React Context. Thus, this hook will only work if there is a parent Formik React Context from which it can pull from. If called without a parent context (i.e. a descendent of a `` component or `withFormik` higher-order component), you will get a warning in your console. diff --git a/docs/api/formik.md b/docs/components/Formik.md similarity index 77% rename from docs/api/formik.md rename to docs/components/Formik.md index db56ee05a..0bdb111e7 100644 --- a/docs/api/formik.md +++ b/docs/components/Formik.md @@ -3,111 +3,70 @@ id: formik title: --- -`` is a component that helps you with building forms. It uses a render -props pattern made popular by libraries like React Motion and React Router. +`` is a component that helps you with building forms. Can be used as a standalone components, or use the child function pattern made popular by libraries like React Motion and React Router. -## Example +The most optimized way to use the Formik component is to pass components as children and use React Context to access Formik's API and State. This is how the components provided with Formik work. It is also possible to pass a custom component to render or a function as children, but these are expensive because Formik will subscribe to its full state and pass those to these components. -```jsx -import React from 'react'; -import { Formik } from 'formik'; - -const BasicExample = () => ( -
-

My Form

- { - setTimeout(() => { - alert(JSON.stringify(values, null, 2)); - actions.setSubmitting(false); - }, 1000); - }} - > - {props => ( -
- - {props.errors.name &&
{props.errors.name}
} - -
- )} -
-
-); -``` - -#### Props +You can read more about this in the [deep dive](../deep-dive). ---- +## Rendering with `` -# Reference +There are 2 recommended ways to render things with `` -## Props +- ``, with components as children. +- `{props => }`, with a function as children. +- ~~``~~ Deprecated in 3.x. It is recommended to use `useFormik` and `FormikProvider` instead. +- ~~``~~ Deprecated in 2.x -### Formik render methods and props +For these examples, we'll use some common Example Helpers. -There are 2 ways to render things with `` +```tsx file=packages/examples/basic/basic-helpers.tsx +``` -- `` -- `` -- ~~``~~ Deprecated in 2.x +### Optimized: Passing Components as Children -Each render methods will be passed the same props: +This is the most optimized way to use the `` component. Child components access Formik's API and State using [hooks](../hooks). -#### `dirty: boolean` +```tsx file=packages/examples/basic/basic.tsx +``` -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. +### Expensive: Passing a Function as Children -#### `errors: { [field: string]: string }` +This is an expensive method of using Formik's state best used for rapid prototyping. It is not recommended in the long term, not only because it is expensive, but also because the function passed here is _not_ a component, and does not have access to hooks! -Form validation errors. Should match the shape of your form's `values` defined -in `initialValues`. If you are using `validationSchema` (which you should be), -keys and shape will match your schema exactly. Internally, Formik transforms raw -[Yup validation errors](https://github.com/jquense/yup#validationerrorerrors-string--arraystring-value-any-path-string) -on your behalf. If you are using `validate`, then that function will determine -the `errors` objects shape. +```tsx file=packages/examples/basic/basic-function-as-children.tsx +``` -#### `handleBlur: (e: any) => void` +### Legacy: `render` prop -`onBlur` event handler. Useful for when you need to track whether an input has -been `touched` or not. This should be passed to `` +This is the same as [passing a function as children](#expensive-passing-a-function-as-children) except it looks like: ` }>`. -#### `handleChange: (e: React.ChangeEvent) => void` +### Legacy: Passing a Custom Component -General input change event handler. This will update the `values[key]` where -`key` is the event-emitting input's `name` attribute. If the `name` attribute is -not present, `handleChange` will look for an input's `id` attribute. Note: -"input" here means all HTML inputs. +This is an expensive method of using Formik's state. It is not recommended in the long term. To customize Formik, it is better to create a custom component and use [`useFormik()`](../hooks#useFormik) with [`FormikProvider`](./FormikProvider). -#### `handleReset: () => void` +```tsx file=packages/examples/basic/basic-custom-component.tsx +``` -Reset handler. Will reset the form to its initial state. This should be passed -to `` +## `FormikProps` -#### `handleSubmit: (e: React.FormEvent) => void` +When using a function as children or a custom component, the props passed will be `FormikProps`. These props combine the Formik API, FormikState, and FormikConfig into a single object. Read more about those types here. -Submit handler. This should be passed to `
...
`. To learn more about the submission process, see [Form Submission](../guides/form-submission.md). +- [FormikApi](./api) +- [FormikState](./state) +- [FormikConfig](./config) -#### `isSubmitting: boolean` -Submitting state of the form. Returns `true` if submission is in progress and `false` otherwise. IMPORTANT: Formik will set this to `true` as soon as submission is _attempted_. To learn more about the submission process, see [Form Submission](../guides/form-submission.md). -#### `isValid: boolean` +--- TODO everything below this line --- -Returns `true` if there are no `errors` (i.e. the `errors` object is empty) and `false` otherwise. +# Reference -> Note: `isInitialValid` was deprecated in 2.x. However, for backwards compatibility, if the `isInitialValid` prop is specified, `isValid` will return `true` if the there are no `errors`, or the result of `isInitialValid` of the form if it is in "pristine" condition (i.e. not `dirty`). +## Props -#### `isValidating: boolean` +### Formik render methods and props -Returns `true` if Formik is running validation during submission, or by calling [`validateForm`] directly `false` otherwise. To learn more about what happens with `isValidating` during the submission process, see [Form Submission](../guides/form-submission.md). +Each render methods will be passed the same props: #### `resetForm: (nextState?: Partial>) => void` diff --git a/docs/components/FormikProvider.md b/docs/components/FormikProvider.md new file mode 100644 index 000000000..835fab411 --- /dev/null +++ b/docs/components/FormikProvider.md @@ -0,0 +1,6 @@ +--- +id: formik-provider +title: +--- + +A custom React Context provider for Formik's API and Config. It receives the value of `useFormik`. The API and Config can be accessed separately via [`useFormikContext()`](../hooks#useFormikContext) and [`useFormikConfig()`](../hooks#useFormikConfig). diff --git a/docs/deep-dive.md b/docs/deep-dive.md new file mode 100644 index 000000000..98a3ab83b --- /dev/null +++ b/docs/deep-dive.md @@ -0,0 +1,157 @@ +--- +id: deep-dive +title: A Deep Dive into Formik +description: Learn how to build forms in React with Formik. +--- + +## Before we start + +Welcome to the Formik deep dive. Here, we will learn everything we need to know about the inner workings of Formik and get prepared to extend and customize Formik. + +If you’re impatient and just want to start hacking on your machine locally, check out [the 60-second quickstart](./overview.md#getting-started). If you just want a quick tutorial on using Formik out of the box, check out the [tutorial](./tutorial.md`). + +### What are we building? + +In this tutorial, we’ll build a complex newsletter signup form with React and Formik. We'll be following the tutorial -- first, using the starter components provided by Formik and gradually customizing Formik with our own custom components, effects, and API methods. + +You can see what we’ll be building here: [Final Result](#final-result). If the code doesn’t make sense to you, don’t worry! The goal of this tutorial is to help you understand Formik. + +### Prerequisites + +You’ll need to have familiarity with HTML, CSS, [modern JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/A_re-introduction_to_JavaScript), [React](https://reactjs.org), and [React Hooks](https://reactjs.org/docs/hooks-intro.html) to fully understand Formik and how it works. In this tutorial, we’ll be using [arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions), [let](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let), [const](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const), [spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax), [destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment), [computed property names](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Computed_property_names), and [async/await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) . You can use the [Babel REPL](https://babeljs.io/repl/#?presets=react&code_lz=MYewdgzgLgBApgGzgWzmWBeGAeAFgRgD4AJRBEAGhgHcQAnBAEwEJsB6AwgbgChRJY_KAEMAlmDh0YWRiGABXVOgB0AczhQAokiVQAQgE8AkowAUAcjogQUcwEpeAJTjDgUACIB5ALLK6aRklTRBQ0KCohMQk6Bx4gA) to check what ES6 code compiles to. + +## Setting Up + +You can follow along with the deep dive similar to the [tutorial](./tutorial.md#setting-up): + +- [Use a Playground](https://codesandbox.io/s/formik-v2-tutorial-start-s04yr) in a new tab. +- [Use a local development environment](./development-setup.md). + +If it is your first time building a React site, it may be faster to jump directly into a Playground. However, if you've followed the tutorial and made it this far, you may want to explore setting up a development environment before digging deep into Formik. + +## Anatomy of a Formik App + +We're going to pick up where we left up with the [Tutorial](./tutorial.md#final-result) and explore how Formik's helper components use the API to manage Formik's state -- building our own Reusable Components in the process. + +Imagine we want to add a newsletter signup form for a blog. To start, our form will have just one field named `email`. To get started with Formik, this is just a few lines of code. + +```tsx file=packages/examples/deep-dive/deep-dive-start.tsx +``` + +As you can see, we're starting with 4 main components from Formik, which are automagically connected and ready for user interaction: + +- `` + - `
` + - `` + - `` + +Let's see how these components come together to create a usable form. + +## The Formik API + +Formik's API works with a hook, `useFormik()`, which returns an API, and a React Context which passes that API down through React's component hierarchy. Child components access this context by using `useFormikContext()`. Let's try it by replacing the `` and `` components with reusable components that do (just about) the same thing: + +```tsx file=packages/examples/deep-dive/deep-dive-useFormik.tsx snippet=use-formik-hooks +``` + +Above, you can see we used the API method `handleSubmit` in order to handle html form submission. Other API methods include `setValues`, `setFieldValue`, `getState()`, and more. [Click here to read more about the Formik API](./api). + +In order to actually submit the form, however, the handleSubmit function must know the latest state of the values within Formik, and access Formik State. + +## Formik State + +Formik State is an immutable object that is replaced entirely when it is updated, similar to how Redux and other Reducers work. It works similar to using `react-redux`, but instead of applying to the whole app, it is scoped to the component which calls `useFormik()`. + +Every time the state is updated, it notifies React of that update via `setState` to trigger a new render when necessary. + +[Click here to find out more about Formik's state](./guides/accessing-state). + +### Using Formik State + +We've seen how the API works and is passed down via React Context in order to allow child components to access the API, and now we know that we know the API accesses and manipulates state. How state is accessed is going to be different depending on the situation, so let's look at the three ways to access state in Formik. + +#### Accessing state outside of a render + +State can be accessed on the fly, outside of a render, similar to how Redux exposes state, using the `getState()` API method. This is useful in event callbacks and setter methods that need to get the result of their actions. + +We can replicate what the `handleSubmit` function is doing from our last example using `getState()`: + +```tsx file=packages/examples/deep-dive/deep-dive-getState.tsx snippet=getState +``` + +`getState()` is useful for: + +- Event callbacks like `onClick`. +- Effects which need to check the result of their calls to the API. +- Debugging the latest state with `console.log` or breakpoints. + +#### Accessing state within a render + +State can be accessed from React Context using the `useFormikState` hook or the `formik.useState` API method. This value can be used in hook dependencies and rendered to html. Let's imagine we want to add a `console.log` anytime `formikState` changes. We can achieve this two ways -- either from our `` component or via the root component with `useFormik`. + +`useFormikState()` and `formik.useState()` are essentially the same, but used in different places. Most of the time, you'll use `useFormikState()` in child components below Formik's context. However, if you are at the root level, you cannot use React Context -- this is where `formik.useState()` comes in. + +`useFormikState()` / `formik.useState()` are useful for: + +- Rendering Values to HTML. +- Triggering dependency updates for `useEffect()` and other hooks. + +##### Accessing state via React Context with `useFormikState()`. + +Accessing Formik's state within a render is possible in components under Formik's React Context by using the `useFormikState` function. This method returns a tuple, and the second value returned is the Formik API. This is a little shortcut for using both state and API methods. + +```tsx packages/examples/deep-dive/deep-dive-optimizing-state.tsx snippet=useFormikState +``` + +##### Accessing state directly from the API using `formik.useState()`. + +Accessing Formik's state within the component calling `useFormik` is done slightly differently than for child components. This is because the React Context doesn't exist yet! + +```tsx packages/examples/deep-dive/deep-dive-optimizing-state.tsx snippet=formik-useState +``` + +#### Optimizing `state` + +As you may have guessed, `useFormikState()` is actually a helpful shortcut for `useFormikContext().useState()`. Both of these methods accept a selector for optimizing the subscription to state. Other parameters include a comparison function, and a boolean to determine whether the change should subscribe and trigger further renders. It is important that the functions passed to these functions are memoized or constant functions, because every time they are updated, the subscription will be recreated, and a render will be triggered in the requesting component. + +Let's imagine we want to add a console.log every time `formikState.values` updates. + +We can either: + +- create a constant function outside of our component: + + ```tsx packages/examples/deep-dive/deep-dive-optimizing-state.tsx snippet=selector + ``` + +- or memoize a function within our component: + + ```tsx packages/examples/deep-dive/deep-dive-optimizing-state.tsx snippet=selector-memoized + ``` + +At the root level, we can pass the callback to `formik.useState()`. In this example, we will always subscribe to updates since we are always passing a function: + +```tsx packages/examples/deep-dive/deep-dive-optimizing-state.tsx snippet=formik-useState +``` + +As a child component, we can also pass the selector to `useFormikState()`. Let's pass the `onChange` function created in the last example to our custom Form component. + +```tsx packages/examples/deep-dive/deep-dive-optimizing-state.tsx snippet=using-form-onChange +``` + +This callback might not always be provided, so we'll also check for the existence of the prop when deciding whether to subscribe: + +```tsx packages/examples/deep-dive/deep-dive-optimizing-state.tsx snippet=optimizing-useFormikState +``` + +##### Using a custom comparison function + +The above function will trigger every time `values` is updated, but it is possible for `values` to update even though the values inside it haven't changed. Instead, we can use deep equality via `lodash` to check for an actual change: + +```tsx packages/examples/deep-dive/deep-dive-optimizing-state.tsx snippet=deep-comparison +``` + +We use this method in `useFieldMeta` to optimize it for when the resulting is updated, but the actual field's `value`, `touched`, and `error` don't change. + +##### Optionally subscribing + +Optionally subscribing to state is a really interesting workaround for the fact that we cannot violate the rules of hooks by optionally calling `useState`. Instead, we can tell the underlying subscription that we don't care about updates and thus avoid renders. We use this within the `` component to provide a highly optimized component by default, but allow users to pass a function as children which is less optimized but always has the latest state. See \ No newline at end of file diff --git a/docs/development-setup.md b/docs/development-setup.md new file mode 100644 index 000000000..41827fcb1 --- /dev/null +++ b/docs/development-setup.md @@ -0,0 +1,60 @@ +--- +id: development-setup +title: Setting up a Development Environment +description: Formik documentation, tutorial, guides, and examples +--- + +Setting up a Development Environment involves a little more than just the workings of Formik. Here, we'll use Create React App to create an out-of-the-box environment to start working with Formik. + +1. Make sure you have a recent version of [Node.js](https://nodejs.org/en/) installed. +1. Follow the [installation instructions for Create React App](https://create-react-app.dev) to make a new project. + +```bash +npx create-react-app my-app +``` + +1. Install Formik + +```bash +npm i formik +``` + +Or + +```bash +yarn add formik +``` + +1. Delete all files in the `src/` folder of the new project + +> Note: +> +> **Don’t delete the entire `src` folder, just the original source files inside it.** We’ll replace the default source files with examples for this project in the next step. + +```bash +cd my-app +cd src + +# If you’re using a Mac or Linux: +rm -f * + +# Or, if you’re on Windows: +del * + +# Then, switch back to the project folder +cd .. +``` + +5. Add a file named `styles.css` in the `src/` folder with [this CSS code](https://codesandbox.io/s/formik-v2-tutorial-start-s04yr?file=/src/styles.css). + +6. Add a file named `index.js` in the `src/` folder with [this JS code](https://codesandbox.io/s/formik-v2-tutorial-start-s04yr?file=/src/index.js:0-759). + +Now run `npm start` in the project folder and open `http://localhost:3000` in the browser. You should see an email input and a submit button. + +We recommend following [these instructions](https://babeljs.io/docs/editors/) to configure syntax highlighting for your editor. + + + +### Help, I’m Stuck! + +If you get stuck, check out Formik’s [GitHub Discussions](https://github.com/formik/formik/discussions). In addition, the [Formium Community Discord Server](https://discord.gg/pJSg287) is a great way to get help quickly too. If you don’t receive an answer, or if you remain stuck, please file an issue, and we’ll help you out. diff --git a/docs/guides/configuring-formik.md b/docs/guides/configuring-formik.md new file mode 100644 index 000000000..a23d1a9cd --- /dev/null +++ b/docs/guides/configuring-formik.md @@ -0,0 +1,8 @@ +--- +id: configuring-formik +title: Configuring Formik +--- + +# Configuring Formik + +(todo) diff --git a/docs/hooks.md b/docs/hooks.md new file mode 100644 index 000000000..e3c193b6c --- /dev/null +++ b/docs/hooks.md @@ -0,0 +1,31 @@ + +## `useFormikContext()` + +`useFormikContext()` is a React hook that will return the [Formik Api](./api) via [React Context](https://reactjs.org/docs/context.html). + +> The old `AutoSubmitToken` example has been modernized and moved to [`useState`](#useState). + +### `useFormikContext()` Example + +Here's an example of using Formik's context in order to get the submit handler: + +```tsx file=packages/examples/tutorial/tutorial-use-formik.tsx snippet=useFormikContext + +``` + +### Reference + +### `useFormikContext(): FormikProps` + +A custom React Hook that returns Formik states and helpers via React Context. Thus, this hook will only work if there is a parent Formik React Context from which it can pull from. If called without a parent context (i.e. a descendent of a `` component or `withFormik` higher-order component), you will get a warning in your console. + +## `useFormikState()` + +A hook to access Formik's State within a render. This is the same as [`useFormikContext().useState()`](./api#useState). + +### `useFormikState()` Example + +Here's an example of a form that works similarly to Stripe's 2-factor verification form. As soon as you type a 6 digit number, the form will automatically submit (i.e. no enter keypress is needed). + +```tsx file=packages/examples/auto-submit/AutoSubmitExample.tsx +``` diff --git a/docs/manifest.json b/docs/manifest.json index 7b248fdd4..ea8d7d241 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -12,6 +12,10 @@ "title": "Tutorial", "path": "/docs/tutorial.md" }, + { + "title": "Deep Dive", + "path": "/docs/deep-dive.md" + }, { "title": "Resources", "path": "/docs/resources.md" diff --git a/docs/overview.md b/docs/overview.md index 412595233..6aa7a1ed7 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -4,20 +4,17 @@ title: Overview description: Formik documentation, tutorial, guides, and examples --- -Let's face it, forms are really verbose in -[React](https://github.com/facebook/react). To make matters worse, most form -helpers do wayyyy too much magic and often have a significant performance cost -associated with them. Formik is a small library that helps you with the 3 most -annoying parts: +## Background + +Formik is a small library that helps you with the 3 most annoying parts of creating forms in React: 1. Getting values in and out of form state 2. Validation and error messages 3. Handling form submission -By colocating all of the above in one place, Formik will keep things -organized--making testing, refactoring, and reasoning about your forms a breeze. +By colocating all of the above in one place, Formik will keep things organized--making testing, refactoring, and reasoning about your forms a breeze. -## Motivation +### Motivation I ([@jaredpalmer](https://twitter.com/jaredpalmer)) wrote Formik while building a large internal administrative dashboard with [@eonwhite](https://twitter.com/eonwhite). With around ~30 unique forms, it @@ -45,10 +42,10 @@ to you.** My talk at React Alicante goes much deeper into Formik's motivation and philosophy, introduces the library (by watching me build a mini version of it), and demos how to build a non-trivial form (with arrays, custom inputs, etc.) using the real thing.
- +
-## Influences +### Influences Formik started by expanding on [this little higher order component](https://github.com/jxnblk/rebass-recomposed/blob/master/src/withForm.js) @@ -59,30 +56,36 @@ Redux-Form, and (most recently) the render props approach popularized by have used any of the above or not, Formik only takes a few minutes to get started with. -## Installation +## Getting Started You can install Formik with [NPM](https://npmjs.com), [Yarn](https://yarnpkg.com), or a good ol' ` ``` -Once you've added this you will have access to the `window.Formik.` variables. +Once you've added this you will have access to Formik via `window.Formik.`. -> This installation/usage requires the [React CDN script bundles](https://reactjs.org/docs/cdn-links.html) to be on the page as well. +> This setup requires the [React CDN script bundles](https://reactjs.org/docs/cdn-links.html) to be on the page as well. -### In-browser Playgrounds +#### In-browser Playgrounds You can play with Formik in your web browser with these live online playgrounds. - [CodeSandbox (ReactDOM)](https://codesandbox.io/s/zKrK5YLDZ) - [Snack (React Native)](https://snack.expo.io/?dependencies=yup%2Cformik%2Creact-native-paper%2Cexpo-constants&sourceUrl=https%3A%2F%2Fgist.githubusercontent.com%2Fbrentvatne%2F700e1dbf9c3e88a11aef8e557627ce3f%2Fraw%2Feeee57721c9890c1212ac34a4c37707f6354f469%2FApp.js) -## The Gist - -Formik keeps track of your form's state and then exposes it plus a few reusable -methods and event handlers (`handleChange`, `handleBlur`, and `handleSubmit`) to -your form via `props`. `handleChange` and `handleBlur` work exactly as -expected--they use a `name` or `id` attribute to figure out which field to -update. - -```jsx -import React from 'react'; -import { Formik } from 'formik'; - -const Basic = () => ( -
-

Anywhere in your app!

- { - const errors = {}; - if (!values.email) { - errors.email = 'Required'; - } else if ( - !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email) - ) { - errors.email = 'Invalid email address'; - } - return errors; - }} - onSubmit={(values, { setSubmitting }) => { - setTimeout(() => { - alert(JSON.stringify(values, null, 2)); - setSubmitting(false); - }, 400); - }} - > - {({ - values, - errors, - touched, - handleChange, - handleBlur, - handleSubmit, - isSubmitting, - /* and other goodies */ - }) => ( - - - {errors.email && touched.email && errors.email} - - {errors.password && touched.password && errors.password} - - - )} - -
-); - -export default Basic; -``` +## Formik in Action -### Reducing boilerplate - -The code above is very explicit about exactly what Formik is doing. `onChange` -> `handleChange`, `onBlur` -> `handleBlur`, and so on. However, to save you time, Formik comes with a few extra components to make life easier and less verbose: `
`, ``, and ``. They use React context to hook into the parent `` state/methods. - -```jsx -// Render Prop -import React from 'react'; -import { Formik, Form, Field, ErrorMessage } from 'formik'; - -const Basic = () => ( -
-

Any place in your app!

- { - const errors = {}; - if (!values.email) { - errors.email = 'Required'; - } else if ( - !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email) - ) { - errors.email = 'Invalid email address'; - } - return errors; - }} - onSubmit={(values, { setSubmitting }) => { - setTimeout(() => { - alert(JSON.stringify(values, null, 2)); - setSubmitting(false); - }, 400); - }} - > - {({ isSubmitting }) => ( - - - - - - - - )} - -
-); - -export default Basic; +Formik provides reusable components that do a lot of heavy lifting. Using these components, Formik can often look as simple as: + +```tsx file=packages/examples/basic/basic.tsx ``` -Read below for more information... +Of course, forms are often more complicated than a few text boxes. In these cases, the same functionality that is used to build Formik's core components is also available to developers to create their own reusable, Formik-connected components. + +## Customizing Formik + +To get started customizing Formik and creating your own custom components, check out the [tutorial](./tutorial.md). -### Complementary Packages +## Complementary Packages As you can see above, validation is left up to you. Feel free to write your own validators or use a 3rd party library. Personally, I use diff --git a/docs/state.md b/docs/state.md new file mode 100644 index 000000000..e3ea81061 --- /dev/null +++ b/docs/state.md @@ -0,0 +1,44 @@ +--- +id: state +title: Formik's State +--- + +## Accessing Formik State + +In Formik there are three methods of accessing state: + +- [formik.getState](./api#getState) - Get the latest state from the API. +- [useFormikState](./hooks#useFormikState) - Get Formik's state for this render. +- [formik.useState](./api#useState) - Get Formik's state for this render, used when you do not have access to Context, but only to `useFormik()`. + +See more information about using Formik's state in the [deep dive](./deep-dive#formik-state). + +## `FormikState` + +### `dirty: boolean` + +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 }` + +Form validation errors. Should match the shape of your form's `values` defined +in `initialValues`. If you are using `validationSchema` (which you should be), +keys and shape will match your schema exactly. Internally, Formik transforms raw +[Yup validation errors](https://github.com/jquense/yup#validationerrorerrors-string--arraystring-value-any-path-string) +on your behalf. If you are using `validate`, then that function will determine +the `errors` objects shape. + +### `isSubmitting: boolean` + +Submitting state of the form. Returns `true` if submission is in progress and `false` otherwise. IMPORTANT: Formik will set this to `true` as soon as submission is _attempted_. To learn more about the submission process, see [Form Submission](../guides/form-submission.md). + +### `isValid: boolean` + +Returns `true` if there are no `errors` (i.e. the `errors` object is empty) and `false` otherwise. + +> Note: `isInitialValid` was deprecated in 2.x. However, for backwards compatibility, if the `isInitialValid` prop is specified, `isValid` will return `true` if the there are no `errors`, or the result of `isInitialValid` of the form if it is in "pristine" condition (i.e. not `dirty`). + +### `isValidating: boolean` + +Returns `true` if Formik is running validation during submission, or by calling [`validateForm`] directly `false` otherwise. To learn more about what happens with `isValidating` during the submission process, see [Form Submission](../guides/form-submission.md). diff --git a/docs/tutorial.md b/docs/tutorial.md index 36e0d1417..f6b6cc984 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -1,150 +1,91 @@ --- -id: tutorial -title: Tutorial +id: deep-dive +title: A Deep Dive into Formik description: Learn how to build forms in React with Formik. --- ## Before we start -Welcome to the Formik tutorial. This will teach you everything you need to know to build simple and complex forms in React. +Welcome to the Formik tutorial. This will teach you everything you need to know to build simple forms in React. -If you’re impatient and just want to start hacking on your machine locally, check out [the 60-second quickstart](./overview#installation). +If you’re impatient and just want to start hacking on your machine locally, check out [the 60-second quickstart](./overview.md#getting-started). If you're looking for a deeper knowledge of Formik, check out the [deep dive](./deep-dive.md). -### What are we building? +## What is Formik? + +Formik is a small group of React components, hooks and helpers for building forms in React and React Native. It helps with the three most annoying parts: + +1. Getting values in and out of form state +2. Validation and error messages +3. Handling form submission -In this tutorial, we’ll build a complex newsletter signup form with React and Formik. +By colocating all of the above in one place, Formik keeps things +organized--making testing, refactoring, and reasoning about your forms a breeze. -You can see what we’ll be building here: [Final Result](https://codesandbox.io/s/formik-v2-tutorial-final-ge1pt). If the code doesn’t make sense to you, don’t worry! The goal of this tutorial is to help you understand Formik. +### What are we building? -### Prerequisites +In this tutorial, we’ll build a newsletter signup form with React and Formik. We'll start with a basic form and add validation, submit logic, etc. -You’ll need to have familiarity with HTML, CSS, [modern JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/A_re-introduction_to_JavaScript), and [React](https://reactjs.org) (and [React Hooks](https://reactjs.org/docs/hooks-intro.html)) to fully understand Formik and how it works. In this tutorial, we’re using [arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions), [let](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let), [const](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const), [spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax), [destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment), [computed property names](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Computed_property_names), and [async/await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) . You can use the [Babel REPL](https://babeljs.io/repl/#?presets=react&code_lz=MYewdgzgLgBApgGzgWzmWBeGAeAFgRgD4AJRBEAGhgHcQAnBAEwEJsB6AwgbgChRJY_KAEMAlmDh0YWRiGABXVOgB0AczhQAokiVQAQgE8AkowAUAcjogQUcwEpeAJTjDgUACIB5ALLK6aRklTRBQ0KCohMQk6Bx4gA) to check what ES6 code compiles to. +You can see what we’ll be building here: [Final Result](#final-result). If the code doesn’t make sense to you, don’t worry! The goal of this tutorial is to help you understand Formik. -## Setup for the Tutorial +## Setting Up There are two ways to complete this tutorial: you can either write the code in your browser, or you can set up a local development environment on your computer. -### Setup Option 1: Write Code in the Browser +### Using a Playground This is the quickest way to get started! First, open this [Starter Code](https://codesandbox.io/s/formik-v2-tutorial-start-s04yr) in a new tab. The new tab should display an email address input, a submit button, and some React code. We’ll be editing the React code in this tutorial. -Skip the second setup option, and go to the [Overview](#overview-what-is-formik) section to get an overview of Formik. - -### Setup Option 2: Local Development Environment +Continue to [What is Formik?](#what-is-formik), or try... -This is completely optional and not required for this tutorial! +### Using a local development environment. -
+To set up your own development environment, including setting up Create React App and running a local server, check out [Development Setup](./development-setup.md). -Optional: Instructions for following along locally using your preferred text editor +This is completely optional and not required for this tutorial! To get started quickly, try [Using a Playground](#using-a-playground). -This setup requires more work, but allows you to complete the tutorial using an editor of your choice. Here are the steps to follow: +## A Basic Formik App -1. Make sure you have a recent version of [Node.js](https://nodejs.org/en/) installed. -1. Follow the [installation instructions for Create React App](https://create-react-app.dev) to make a new project. +We're going to start with a simple app and explore how Formik's helper components can help us to create interactive forms. Imagine we want to add a newsletter signup form for a blog. We'll start off with a basic functional component: -```bash -npx create-react-app my-app +```tsx file=packages/examples/tutorial/tutorial-start.tsx ``` -1. Install Formik +As you can see, we're starting with 4 main components from Formik, which are automagically connected and ready for user interaction: -```bash -npm i formik -``` +- `` + - `
` + - `` + - `` -Or +Let's see how these components come together to create a usable form. -```bash -yarn add formik -``` +### [``](./components/Formik.md) -1. Delete all files in the `src/` folder of the new project +`` is a helper component which creates a Formik instance and passes it down the hierarchy via React context. It also contains the main configuration for how Formik should work, including the initial values of the form, how to validate the form at the form level, and what to do on submit. -> Note: -> -> **Don’t delete the entire `src` folder, just the original source files inside it.** We’ll replace the default source files with examples for this project in the next step. +> ⚠ `Formik.initialValues` and `Formik.onSubmit` are both required properties due to the way that Formik works internally. -```bash -cd my-app -cd src +To read more about configuring Formik, check out [Configuring Formik](./guides/configuring-formik.md). -# If you’re using a Mac or Linux: -rm -f * - -# Or, if you’re on Windows: -del * - -# Then, switch back to the project folder -cd .. -``` - -5. Add a file named `styles.css` in the `src/` folder with [this CSS code](https://codesandbox.io/s/formik-v2-tutorial-start-s04yr?file=/src/styles.css). - -6. Add a file named `index.js` in the `src/` folder with [this JS code](https://codesandbox.io/s/formik-v2-tutorial-start-s04yr?file=/src/index.js:0-759). - -Now run `npm start` in the project folder and open `http://localhost:3000` in the browser. You should see an email input and a submit button. - -We recommend following [these instructions](https://babeljs.io/docs/editors/) to configure syntax highlighting for your editor. - -
- -### Help, I’m Stuck! - -If you get stuck, check out Formik’s [GitHub Discussions](https://github.com/formik/formik/discussions). In addition, the [Formium Community Discord Server](https://discord.gg/pJSg287) is a great way to get help quickly too. If you don’t receive an answer, or if you remain stuck, please file an issue, and we’ll help you out. - -## Overview: What is Formik? - -Formik is a small group of React components and hooks for building forms in React and React Native. It helps with the three most annoying parts: - -1. Getting values in and out of form state -2. Validation and error messages -3. Handling form submission +### `` -By colocating all of the above in one place, Formik keeps things -organized--making testing, refactoring, and reasoning about your forms a breeze. +`` is a helper component which replaces the html `` element. It is connected to Formik, so that submitting the form via the normal HTML methods will run validations and trigger `Formik.onSubmit`. -## The Basics +### `` -We’re going to start with the _most verbose_ way of using Formik. While this may seem a bit long-winded, it’s important to see how Formik builds on itself so you have a full grasp of what’s possible and a complete mental model of how it works. +`` is a helper component which replaces the html `` element. It is connected to Formik, so that submitting the form via the normal HTML methods will run validations and trigger `Formik.onSubmit`. -### A simple newsletter signup form +### `` -Imagine we want to add a newsletter signup form for a blog. To start, our form will have just one field named `email`. With Formik, this is just a few lines of code. +`` is a helper component which represents the html ``, `