mrkeyoor.com_
Sat 08 Aug 20:59 UTC
npmWeb Frontendupdated 08 Aug 2026

formik

Formik is a React-only form state manager. It keeps values, errors, touched fields, submission state, and validation together, then exposes them through components such as Formik, Form, Field, ErrorMessage, and FieldArray or through hooks such as useFormik and useField. It does not render a design system or decide how inputs should look. Its main appeal is a mature, explicit controlled-form model with built-in support for nested field paths, array helpers, field-level validation, whole-form validation, and optional Yup schemas.

Verdict

Formik 2 remains a dependable maintenance choice for applications already built around it. For a new performance-sensitive form system, React Hook Form or TanStack Form is usually the better starting point because Formik's controlled model, bundle weight, and backlog are real costs.

API stability4/5Formik 2's main contracts, including Formik, Field, FieldArray, useFormik, useField, values, errors, touched, and helper methods, have remained recognizable for years. Version 2.4.9 even carries compatibility forward to React 19, but a published 3.0.0-next line and deprecated render props show that not every older usage style belongs in new code.
Docs4/5formik.org has a tutorial, API pages for every component and hook, guides for validation, TypeScript, arrays, submission, and React Native, plus runnable examples. The pages document defaults such as validation timing and enableReinitialize, but some examples still reflect older React style and the important FieldArray error-shape caveat is easy to miss.
Maintenance3/5The stable 2.4.9 release shipped in November 2025 with a React 19 fix, so the package is maintained rather than abandoned. Still, the repository's last push was that release day and GitHub search showed 702 open issues, while the repository API counted 836 open issues and pull requests together, making response time and backlog risk hard to ignore.
Ecosystem5/5Formik recorded 4,394,571 downloads for the measured week and has 34,329 GitHub stars. UI libraries, tutorials, schema validators, and existing React codebases widely document its props and hooks, and React versions from 16.8 through 19 fit its peer range. Its conventions are familiar even to developers who no longer choose it for greenfield forms.

Use it if

  • You maintain an existing React application whose forms already use Formik 2 and consistency matters more than switching abstractions
  • Your team prefers controlled inputs and wants values, touched state, errors, validation, and submission in one predictable object
  • You need nested object fields, repeatable arrays, or both field-level and form-level validation
  • You want a mature React form library with TypeScript declarations and extensive examples for common UI component integrations
Skip it if

Setup reality

Install formik beside React; version 2.4.9 declares React 16.8 or newer as its only peer dependency and includes its own TypeScript declarations. Yup is not included, even though much of the documentation uses validationSchema, so install yup separately or provide a validate function. Every controlled field should exist in initialValues, including empty strings and empty arrays, or React can warn about uncontrolled inputs becoming controlled. Field names are string paths such as user.email or friends[0].name, which is convenient at runtime but not checked against your Values type by helpers such as setFieldValue. Validation runs after changes and blur events by default, which can feel slow when a schema is expensive; tune validateOnChange, validateOnBlur, or call validateForm at deliberate points. Formik marks fields touched on blur, so displaying errors only when touched depends on wiring handleBlur or the props returned by Field and useField. An async onSubmit that returns a Promise clears isSubmitting when it settles, while a synchronous handler must call setSubmitting(false) itself. New initialValues do not reset an existing form unless enableReinitialize is true, and that option uses deep equality, so remote record editors need a deliberate loading and reset strategy. FieldArray validation errors can be either a string for the whole array or a nested array of item errors, which makes naive rendering crash or print objects. Custom design-system controls must forward name, value, onChange, and onBlur correctly. Formik 2.4.9 includes a React 19 JSX-ref fix, but the stable API is still version 2 while a 3.0.0 next tag exists, so do not build production code against the prerelease unless you are prepared for migration work.

Patterns

Build a typed form with Formik componentsbuild-basic-form

import { ErrorMessage, Field, Form, Formik } from 'formik';

type Values = { email: string };

export function SignupForm() {
  return (
    <Formik<Values>
      initialValues={{ email: '' }}
      validate={(values) => (!values.email ? { email: 'Required' } : {})}
      onSubmit={async (values) => saveSignup(values)}
    >
      <Form>
        <label htmlFor="email">Email</label>
        <Field id="email" name="email" type="email" />
        <ErrorMessage name="email" component="p" />
        <button type="submit">Sign up</button>
      </Form>
    </Formik>
  );
}

Put every controlled field in initialValues. Form and Field wire submit, change, and blur handlers through Formik context.

Use Formik without its wrapper componentsuse-formik-hook

import { useFormik } from 'formik';

const formik = useFormik({
  initialValues: { query: '' },
  validate: ({ query }) => (query.trim() ? {} : { query: 'Enter a query' }),
  onSubmit: async (values) => runSearch(values.query),
});

return (
  <form onSubmit={formik.handleSubmit}>
    <input name="query" {...formik.getFieldProps('query')} />
    {formik.touched.query && formik.errors.query && <p>{formik.errors.query}</p>}
    <button type="submit">Search</button>
  </form>
);

useFormik does not provide Formik context, so Field, ErrorMessage, FieldArray, and useFormikContext cannot consume this instance.

Validate with a Yup schemavalidate-with-yup

import * as Yup from 'yup';

const schema = Yup.object({
  email: Yup.string().email('Invalid email').required('Required'),
  age: Yup.number().integer().min(18, 'Must be 18 or older').required('Required'),
});

<Formik
  initialValues={{ email: '', age: 18 }}
  validationSchema={schema}
  onSubmit={saveProfile}
>
  {form}
</Formik>

Yup is a separate package. Formik maps Yup errors to keys that mirror values and touched.

Connect a custom input with useFieldwrite-custom-field

import { useField } from 'formik';

function TextInput({ label, ...props }: { label: string; name: string; type?: string }) {
  const [field, meta] = useField(props);
  return (
    <label>
      {label}
      <input {...field} {...props} />
      {meta.touched && meta.error ? <span role="alert">{meta.error}</span> : null}
    </label>
  );
}

Spread field before props when callers should be allowed to override presentation attributes, but do not replace Formik's name, value, onChange, or onBlur accidentally.

Add and remove repeated fieldsmanage-field-array

import { Field, FieldArray } from 'formik';

<FieldArray name="friends">
  {({ form, push, remove }) => (
    <div>
      {form.values.friends.map((friend: { name: string }, index: number) => (
        <div key={index}>
          <Field name={`friends[${index}].name`} />
          <button type="button" onClick={() => remove(index)}>Remove</button>
        </div>
      ))}
      <button type="button" onClick={() => push({ name: '' })}>Add friend</button>
    </div>
  )}
</FieldArray>

Array helpers update touched state and trigger validation. Prefer stable item IDs as React keys once rows have persisted identities.

Update one field from another controlset-dependent-field

import { useFormikContext } from 'formik';

type Values = { country: string; state: string };

function CountrySelect() {
  const { values, setFieldValue } = useFormikContext<Values>();
  return (
    <select
      name="country"
      value={values.country}
      onChange={async (event) => {
        await setFieldValue('country', event.target.value);
        await setFieldValue('state', '', false);
      }}
    >
      <option value="">Choose</option>
      <option value="US">United States</option>
    </select>
  );
}

The third setFieldValue argument controls validation. String paths and values are not fully checked against the Values type.

Submit asynchronously and show progresshandle-async-submit

<Formik
  initialValues={{ title: '' }}
  onSubmit={async (values, { setStatus }) => {
    try {
      await api.create(values);
      setStatus({ saved: true });
    } catch (error) {
      setStatus({ message: getErrorMessage(error) });
    }
  }}
>
  {({ isSubmitting, status }) => (
    <Form>
      <Field name="title" />
      <button disabled={isSubmitting} type="submit">{isSubmitting ? 'Saving...' : 'Save'}</button>
      {status?.message && <p role="alert">{status.message}</p>}
    </Form>
  )}
</Formik>

When onSubmit returns a Promise, Formik resets isSubmitting after it settles. A synchronous handler must call setSubmitting(false).

Map API validation errors back to fieldsmap-server-errors

async function submit(values: Values, helpers: FormikHelpers<Values>) {
  const result = await api.update(values);
  if (!result.ok) {
    helpers.setErrors({
      email: result.fieldErrors.email,
      username: result.fieldErrors.username,
    });
    helpers.setStatus({ message: result.message });
    return;
  }
  helpers.resetForm({ values: result.user });
}

Use setErrors for errors tied to named fields and status for form-wide failures. Do not replace errors with keys that have no matching value path.

Reset dirty state after a successful savereset-saved-baseline

onSubmit={async (values, { resetForm }) => {
  const saved = await api.save(values);
  resetForm({ values: saved });
}}

Passing values to resetForm makes the saved response the new initial state, so dirty becomes false against the server-confirmed data.

Reinitialize an editor when its record changesload-new-record

<Formik
  initialValues={{
    title: record?.title ?? '',
    body: record?.body ?? '',
  }}
  enableReinitialize
  onSubmit={saveRecord}
>
  <EditorFields />
</Formik>

enableReinitialize resets when initialValues changes by deep equality and can discard unsaved edits; confirm navigation or key the form when that behavior is intentional.

Run expensive validation on blur or submitdelay-expensive-validation

<Formik
  initialValues={{ code: '' }}
  validate={validateExpensiveRules}
  validateOnChange={false}
  validateOnBlur
  onSubmit={submitCode}
>
  <Form>
    <Field name="code" />
    <button type="submit">Check</button>
  </Form>
</Formik>

Change validation defaults to true. Turning it off reduces work while typing, but errors will remain stale until blur or submit.

Bind several checkboxes to one arraybind-checkbox-group

<Formik initialValues={{ roles: [] as string[] }} onSubmit={saveRoles}>
  <Form>
    <label><Field type="checkbox" name="roles" value="admin" /> Admin</label>
    <label><Field type="checkbox" name="roles" value="editor" /> Editor</label>
    <button type="submit">Save</button>
  </Form>
</Formik>

Checkboxes sharing a name and carrying value props are collected into an array; a single boolean checkbox should omit the value prop.

Alternatives

PackageRegistryPick it when
react-hook-formnpmNew React forms where smaller runtime cost and field-level subscriptions matter more than a controlled state object
react-final-formnpmYou want subscription-based updates and the framework-neutral Final Form state engine
@tanstack/react-formnpmYou want a newer headless form model with stronger TypeScript inference and granular reactivity