# The Formspree React library

> Formspree Docs · Working with React · Updated January 8, 2026

## Installation

This library assumes that React is already installed in your environment as a peer dependency. Our helpers rely on [**React Hooks**](https://reactjs.org/docs/hooks-intro.html), so **you must be on version 16.8.0 or higher**.

```bash
npm install @formspree/react
```

[**Source on GitHub**](https://github.com/formspree/formspree-react) | **[npm package](https://www.npmjs.com/package/@formspree/react)**  
  
You can use this library with or without the Formspree CLI. This article assumes you're creating forms in the dashboard. For help with CLI projects, see our article on using the [Formspree CLI](/articles/using-the-cli/the-formspree-cli/).

## Usage

The `useForm` [**React hook**](https://reactjs.org/docs/hooks-intro.html) is the easiest way to setup a React form with Formspree. Just import `@formspree/react` and then call `useForm` with the form's [hashid](/articles/the-forms-api/getting-your-form-s-hashid/) obtained by creating a form in the Formspree dashboard. 

```jsx
import { useForm } from '@formspree/react';

function MyForm() {
  const [state, handleSubmit, reset] = useForm('{your-form-id}');
  if (state.succeeded) {
    return <div>Thank you for signing up!</div>;
  }
  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="email">Email</label>
      <input id="email" type="email" name="email" />
      <button type="submit" disabled={state.submitting}>Sign up</button>
    </form>
  )
}
```

## State object

The first value in the array returned by this hook is a state object:

```jsx
const [state, handleSubmit, reset] = useForm('{your-form-id}');
```

The state object contains the following properties:

| Key | Description |
| --- | --- |
| `submitting` | A Boolean indicating whether the form is currently submitting (defaults to `false`) |
| `succeeded` | A Boolean indicating whether the form successfully submitted (defaults to `false`) |
| `errors` | An instance of the `SubmissionError`class containing of server-side validation errors (defaults to `null`) |
| `result` | An object with one property `next` which is the redirect URL when the submission is successful (defaults to `null`) |

State changes over time in the following ways:

-   When `handleSubmit` is called, `submitting` becomes `true`
-   If the submission fails server-side validations, a new instance of `errors` is created with the specific errors
-   If the submission succeeds, `succeeded` becomes `true`, and `result` is not `null`
-   After the submission request finishes, `submitting` always becomes `false`

The `errors` object has following methods:

| Key | Description |
| --- | --- |
| `getFormErrors()` | Returns an array of form-level errors |
| `getFieldErrors(field)` | Returns an array of errors for the `field` |
| `getAllFieldErrors()` | Returns an array of key-value pair of field-level errors of all fields (`[field, errors][]`) |

The error items in the `errors` object have the following properties:

| Key | Description |
| --- | --- |
| `message` | A human-readable error message fragment (e.g. "is required") |
| `code` | A machine-friendly error code (See [error codes](#errorcodes) below) |
| `details` | An object containing various additional properties about the error. For example, when used with Stripe, will contain a `stripeCode` field that contains the exact error from Stripe |

## Reset

The third value in the array returned by the hook is a reset function. 

```jsx
const [state, handleSubmit, reset] = useForm('{your-form-id}');
```

This function is used to reset the state object after a form submission request has been completed. It clears out the `result` and `errors` properties, and sets the `submitting` and`succeeded` boolean properties to their default state (i.e. false). You can use this method on the "thank you" page you show after the form submission request has been completed to allow the user to reset the form and submit it once again.

Here's an example showing how to use the `reset()` function in a form:

```jsx
function TestForm() {
    const [state, submit, reset] = useForm('{your-form-id}');

    if (state.submitting) {
        return <p>Submitting…</p>;
    }

    if (state.succeeded) {
        return (
            <div>
                <p>Thanks!</p>;<button onClick={reset}>Reset</button>
            </div>
        );
    }

    return (
        <div>
            <h1>Form</h1>
            <form onSubmit={submit}>
                <label htmlFor="email">Email</label>
                <input
                    id="email"
                    type="email"
                    name="email"
                    defaultValue="test@example.com"
                />

                <button type="submit">Sign up</button>

            </form>
        </div>
    );
}
```

## Validation errors

Here's an example form using the `ValidationError` component to display field errors:

```jsx
import { ValidationError, useForm } from '@formspree/react';

function MyForm() {
  const [state, handleSubmit] = useForm('{your-form-id}');
  if (state.succeeded) {
    return <div>Thank you for signing up!</div>;
  }
  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="email">Email</label>
      <input id="email" type="email" name="email" required />
      <ValidationError field="email" prefix="Email" errors={state.errors} />
      <button type="submit" disabled={state.submitting}>Sign up</button>
    </form>
  )
}
```

The `ValidationError` component accepts the following special properties:

-   `field` — the name of the field for which to display errors. If no `field` attribute is provided then general (non field) errors will be reported. (See [Error codes](#errorcodes) below)
-   `errors` — the object containing validation errors (required)
-   `prefix` — the human-friendly name of the field (optional, defaults to "This field")

All other props (such as `className`) are passed through to the `<div>` wrapper. If the given field is invalid, this component renders a `<div>` containing the error message:

```jsx
<div {...props}>
  {prefix} {message}
</div>
```

## Additional options

This hook accepts two arguments: the form key and an object containing options.

```jsx
const [state, handleSubmit, reset] = useForm('{your-form-key}', options);
```

Here are the acceptable options:

| Key | Type | Description |
| --- | --- | --- |
| `data` | object | An Object containing strings or functions to merge with your form data |
| `client` | Formspree | An instance of the Formspree client |

**Usage Example**

```jsx
const [state, handleSubmit, reset] = useForm('{your-form-id}', {
  data: {
    subject: 'Someone joined the newsletter',
    pageTitle: function() {
      // This function will be evaluated at submission time
      return document.title;
    }
  }
});
```

## Usage with Stripe

The Formspree React library includes support for payments via Stripe. The library handles most of the front end state management required to create payments and perform [Strong Customer Authentication (SCA)](https://stripe.com/docs/strong-customer-authentication). The Formspree backend then takes care of submitting payments to Stripe. Together, Formspree dramatically reduces the development effort required to create a payment form.

Stripe functionality is lazy loaded. That means only a minimal stripe wrapper (just 6k compressed) is bundled with `@formspree/react`. The bulk of the Stripe library is loaded only if, and when, Stripe elements are first rendered.

To use Stripe, first wrap your app with the `FormspreeProvider` component and include the `stripePK` prop with your Stripe publishable key.

```jsx
import { FormspreeProvider } from '@formspree/react';

function MyApp() {
  return (
    <FormspreeProvider
      stripePK={'{your-stripe-publishable-key}'}
    >
      <PaymentForm />
    </FormspreeProvider>
  )
}
```

Then when building your form, use the `CardElement` component exported by `@formspree/react`. 

```jsx
import { useForm, CardElement, ValidationError } from '@formspree/react';

function PaymentForm() {
  const [state, handleSubmit] = useForm('{your-form-id}');

  if (state.succeeded) {
    return <div>Payment has been handled successfully!</div>;
  }

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="email">Email</label>
        <input id="email" type="email" name="email" />
      </div>
      <div>
        <label>Card details</label>
        <CardElement />
        <ValidationError
          field="paymentMethod"
          errors={state.errors}
        />
      </div>
      <button type="submit" disabled={state.submitting}>
        {state.submitting ? 'Handling payment...' : 'Pay'}
      </button>
    </form>
  )
}
```

The `CardElement` component is passed-through directly from `@stripe/react-stripe-js`. It is exported by Formspree React only to ensure version consistency. The documentation for the `CardElement` component can be found [here](https://stripe.com/docs/payments/accept-card-payments?platform=web&ui=elements&html-or-react=react#add-and-configure-a-component).

Note the field name "paymentMethod" on `ValidationError`. This is the field name used to send data from Stripe to Formspree, and must be passed to `ValidationError` to catch relevant Stripe errors.

## Usage with an external form library

The `useSubmit` [**React hook**](https://reactjs.org/docs/hooks-intro.html) is an easy way to integrate another form library with Formspree. Just import `@formspree/react` and then call `useSubmit` with the form's [hashid](/articles/the-forms-api/getting-your-form-s-hashid/) obtained by creating a form in the Formspree dashboard.

```tsx
import { useSubmit } from '@formspree/react';
import { useForm } from 'react-hook-form';

type Inputs = {
  email: string;
  message: string;
  name: string;
};

export function WithReactHookForm() {
  const {
    formState: { errors, isSubmitSuccessful, isSubmitting },
    handleSubmit,
    register,
    setError,
  } = useForm<Inputs>();

  const submit = useSubmit<Inputs>(
    process.env.REACT_APP_REACT_HOOK_FORM_ID as string,
    {
      onError(errs) {
        const formErrs = errs.getFormErrors();
        for (const { code, message } of formErrs) {
          setError(`root.${code}`, {
            type: code,
            message,
          });
        }

        const fieldErrs = errs.getAllFieldErrors();
        for (const [field, errs] of fieldErrs) {
          setError(field, {
            message: errs.map((e) => e.message).join(', '),
          });
        }
      },
    }
  );

  return (
    <div>
      {isSubmitSuccessful ? (
        <h2>Your message has been sent successfully!</h2>
      ) : (
        <form onSubmit={handleSubmit(submit)}>
          <div className="block">
            <label htmlFor="email">Email</label>
            <input {...register('email')} id="email" type="email" />
            {errors.email && <p className="error">{errors.email.message}</p>}
          </div>
          <div className="block">
            <label htmlFor="name">Name</label>
            <input {...register('name')} id="name" />
            {errors.name && <p className="error">{errors.name.message}</p>}
          </div>
          <div className="block">
            <label htmlFor="message">Message</label>
            <textarea {...register('message')} id="message" rows={10} />
          </div>
          {errors.root && (
            <div className="block">
              <ul className="error">
                {Object.values(errors.root).map((err) => {
                  if (typeof err !== 'object') {
                    return <li key={err}>{err}</li>;
                  }
                  const { type, message } = err;
                  return <li key={type}>{message}</li>;
                })}
              </ul>
            </div>
          )}
          <button type="submit" disabled={isSubmitting}>
            {isSubmitting ? 'Submitting...' : 'Submit'}
          </button>
        </form>
      )}
    </div>
  );
}
```

## Error codes

The following error codes may appear in the state object's errors array:

| Code | Field Error | Description |
| --- | --- | --- |
| `INACTIVE` |   | The form has been disabled |
| `BLOCKED` |   | The form has been blocked |
| `EMPTY` |   | No data was submitted |
| `PROJECT_NOT_FOUND` |   | An invalid project key was used to submit the form |
| `FORM_NOT_FOUND` |   | An invalid form hashid was used to submit the form |
| `NO_FILE_UPLOADS` |   | File uploads are not supported for this form |
| `TOO_MANY_FILES` |   | The form was submitted with too many file attachments |
| `FILES_TOO_BIG` |   | One or more files uploaded exceeded the max file size |
| `REQUIRED_FIELD_MISSING` | ✓ | A field is required, but no value was provided |
| `REQUIRED_FIELD_EMPTY` | ✓ | A field is required, but a blank or empty string was provided |
| `TYPE_EMAIL` | ✓ | A field should contain an email |
| `TYPE_NUMERIC` | ✓ | A field should contain a number |
| `TYPE_TEXT` | ✓ | A field should contain text |
| `STRIPE_CLIENT_ERROR` | ✓ | Stripe key missing |
| `STRIPE_SCA_ERROR` | ✓ | Stripe SCA error. See `details` |
