skip to content

Formik

Formik manages form values, validation, touched state, and submission through its components and hooks. It was the React form standard for years, so interviews often ask you to compare its controlled-state model with React Hook Form's uncontrolled one.

on this pageshow

questions

6

In Formik 2, what do <Formik>, <Form>, <Field> and <ErrorMessage> each wire up, and when would you call useFormik directly instead?

level: juniorimportance: must knowfreq 45%

answer

  1. one provider, three consumers
  2. value and handlers from context
  3. error shown only when touched
  4. the hook has no context

basics

~20 s

In Formik 2, <Formik> owns the form state and provides it through context; <Form> wires submit and reset; <Field> binds an input to values, handleChange and handleBlur; <ErrorMessage> shows a touched field's error. useFormik returns the same state without context.

solid answer

~40 s

`<Formik initialValues onSubmit validationSchema>` creates the form state — `values`, `errors`, `touched`, `isSubmitting` — and puts it in React context, also passing it to a render-prop child. `<Form>` is a `<form>` whose `onSubmit` and `onReset` are already `handleSubmit` and `handleReset`. `<Field name="email" />` renders an `<input>` (or whatever `as`/`component` says) with `name`, `value` from `values.email`, and `onChange`/`onBlur` set to `handleChange`/`handleBlur`. `<ErrorMessage name="email" />` renders `errors.email` only when `touched.email` is also set. `useFormik(config)` returns the same state and helpers **without** a context provider, so `<Field>`, `<FastField>`, `<ErrorMessage>`, `<FieldArray>` and `connect()` do not work with it; the docs say to use it only when you are not using `<Formik>`, wiring inputs yourself with `formik.getFieldProps('email')`.

code

tsx · 29 lines
tsx
import { ErrorMessage, Field, Form, Formik } from 'formik';
import * as Yup from 'yup';

const schema = Yup.object({
  email: Yup.string().email('Invalid email').required('Required'),
  plan: Yup.string().required('Pick a plan'),
});

export function NewsletterForm({ subscribe }: { subscribe: (v: { email: string; plan: string }) => Promise<void> }) {
  return (
    <Formik initialValues={{ email: '', plan: '' }} validationSchema={schema} onSubmit={subscribe}>
      {({ isSubmitting }) => (
        <Form>
          <Field name="email" type="email" />
          <ErrorMessage name="email" component="p" />

          <Field name="plan" as="select">
            <option value="">Choose...</option>
            <option value="weekly">Weekly</option>
            <option value="monthly">Monthly</option>
          </Field>
          <ErrorMessage name="plan" component="p" />

          <button type="submit" disabled={isSubmitting}>Subscribe</button>
        </Form>
      )}
    </Formik>
  );
}

go deeper

for a junior

Build a small form with <Formik>, <Form>, <Field> and <ErrorMessage> from memory and name what each one wires.

for a middle

Explain the context split between owner and consumers, and why useFormik breaks every context-based component.

for a senior

Choose between <Formik> with reusable field components and useFormik for a one-off form, and keep initialValues complete.

for a principal

Decide whether a codebase's forms standardise on Formik's component API and how shared field components consume its context.

## The pieces and what each one owns Formik 2 splits a form into one **owner** and several **consumers**. The owner holds state; the consumers read it and report events back. | Piece | Role | What it wires | |---|---|---| | `<Formik>` | Owner | Creates `values`, `errors`, `touched`, `isSubmitting`, `submitCount`; provides them via context and to a render-prop child | | `<Form>` | Consumer | A `<form>` with `onSubmit={handleSubmit}` and `onReset={handleReset}` | | `<Field>` | Consumer | An input bound to `values[name]`, `handleChange`, `handleBlur` | | `<ErrorMessage>` | Consumer | Renders `errors[name]` only when `touched[name]` is set | | `useFormik()` | Owner without context | Returns the same state and helpers as a plain object | ## `<Formik>`: the owner `<Formik>` takes the form's configuration: - `initialValues` — required; the starting value for every field, and the shape Formik uses for `touched` on submit. - `onSubmit(values, formikBag)` — called after validation passes. - `validate` or `validationSchema` — a function returning an errors object, or a Yup schema. - `validateOnChange`, `validateOnBlur` (both default `true`), `validateOnMount` (default `false`), `enableReinitialize` (default `false`). Its child can be a function receiving the whole bag — `{ values, errors, touched, handleSubmit, isSubmitting, ... }` — or ordinary elements that read the bag through context. ## `<Form>` and `<Field>`: wiring inputs `<Form>` saves you writing `<form onSubmit={formik.handleSubmit}>`. `<Field name="email" />` saves you writing: ```tsx <input name="email" value={formik.values.email} onChange={formik.handleChange} onBlur={formik.handleBlur} /> ``` `handleChange` finds the field to update from the input's **`name`** attribute (falling back to `id`), which is why every input needs one. It also converts `type="number"` and `type="range"` values with `parseFloat` and handles checkboxes and multi-selects. `<Field>` can render something else with `as="select"`, `as="textarea"` or `as={MyInput}`, or accept a child function that receives `{ field, form, meta }`. ## `<ErrorMessage>`: the touched gate `<ErrorMessage name="email" />` renders nothing unless **both** `touched.email` and `errors.email` are set. That is Formik's built-in answer to "don't shout at users about fields they have not reached yet": the error may already exist in `errors`, but it shows only once the field has been blurred or the form submitted. Use `component="p"` to choose the wrapper, or a child function to format the message. ## `useFormik`: when and why not `useFormik(config)` is the hook `<Formik>` is built on. It returns the same values and helpers, but it **does not render a context provider**. The documentation is explicit: 1. It is "not meant for the majority of use cases". 2. `<Field>`, `<FastField>`, `<ErrorMessage>`, `connect()` and `<FieldArray>` will **not** work with it, because they all read context. 3. If you want the state through context in a child component, use `useFormikContext()` under a `<Formik>` instead. So `useFormik` suits a small, single-component form where you wire inputs by hand, typically with `formik.getFieldProps('email')`, which returns `{ name, value, onChange, onBlur }`. As soon as you want reusable field components, use `<Formik>`. ## Common mistakes - **Omitting `name` on an input** wired to `handleChange`: Formik cannot tell which value to update. - **Using `<ErrorMessage>` with `useFormik`** and wondering why nothing renders. - **Leaving fields out of `initialValues`**: they start as `undefined`, which can make React warn about an uncontrolled input becoming controlled, and they are not marked touched on submit. - **Expecting `<ErrorMessage>` to show errors immediately** after typing, before the field has been touched.

  • In Formik 2, how does a deeply nested child component read the form's values without prop drilling?
    Call `useFormikContext()` in the child. It reads the context that `<Formik>` provides and returns the same bag — `values`, `errors`, `setFieldValue` and so on. It only works under `<Formik>` or `withFormik`, not under a form built with `useFormik()`, which provides no context.
  • What does getFieldProps return in Formik 2, and why is it useful with useFormik?
    `formik.getFieldProps('email')` returns `{ name, value, onChange, onBlur }` for that field — exactly what `<Field>` would inject. Spreading it onto an `<input>` gives you `<Field>`-style wiring in a `useFormik` form, where `<Field>` itself cannot be used because there is no context.

saying these in an interview costs you the question

  • <Field> renders an uncontrolled input that Formik reads on submit
  • useFormik and <Formik> are interchangeable, so <ErrorMessage> works with both
  • <ErrorMessage> shows the error as soon as validation produces it
  • handleChange works without a name or id on the input
  • initialValues is optional; Formik infers fields from the inputs it sees
open as a page

In Formik 2, a field's error already exists in errors while the user types, yet no message shows until they leave the field — why?

level: middleimportance: should knowfreq 40%

basics

~20 s

Formik 2 validates the whole form on every change and blur by default, so errors fills in while the user types. touched.field is set only by blur or submit, and the usual display gate — including ErrorMessage — needs both.

open as a page

A Formik 2 form's submit button, disabled by isSubmitting, stays disabled forever after the first submit — what in onSubmit causes that?

level: middleimportance: should knowfreq 38%

basics

~10 s

Formik 2 resets isSubmitting only when onSubmit returns a promise, once it settles. A synchronous onSubmit, or one that starts a request without returning it, leaves isSubmitting true until you call setSubmitting(false).

open as a page

In Formik 2, how do you build a reusable custom input with useField, and what do the three values it returns hold?

level: middleimportance: should knowfreq 35%

basics

~20 s

In Formik 2, useField(name) returns [field, meta, helpers]: field holds name, value, onChange and onBlur to spread onto an input; meta holds value, error, touched and initial values; helpers holds setValue, setTouched and setError for non-event widgets.

open as a page

A Formik 2 edit-profile form renders before the user record loads and stays empty after it arrives — how do you fix it, and what does enableReinitialize risk?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Formik 2 reads initialValues once, at mount. enableReinitialize resets the form whenever initialValues changes by deep equality, which discards unsaved edits if the loaded data changes again; rendering after load or keying by user id avoids that.

open as a page

A 150-field Formik 2 form lags while typing because every keystroke re-renders every field — why, and when does FastField help or go stale?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Formik 2 keeps values in the form owner's state, so each keystroke re-renders the owner and every Field under it. FastField re-renders only when its own value, error or touched, or isSubmitting, changes — so it goes stale if it depends on another field.

open as a page