Chris Padilla/Blog / Tech

Customizing Field State in React Final Form

React Final Form is a delightful library. So much comes out of the box, and it's extensible enough to handle custom solutions.

I've been working with it a fair amount recently. Here's what adding custom logic looks like:

Scenario

Say that you have data that needs to update two fields within your form. An example might be that you have an event form with the fields:

  • Name: Skate-a-thon 2022
  • Start Date: August 3rd
  • End Date: August 5th

Let's also say that Skate-a-thon got rescheduled to another weekend.

When going in to push back the start date, we want our form logic to automatically update the end date as well - to also go back 7 days.

Simple Set up In Final Form

I'll leave the initial set up to the React Final Form docs.

Let's pick up with a component that looks like this within our form:

<h2>Render Function as Children</h2>
<Field name="name">
  {({ input, meta }) => (
    <div>
      <label>Name</label>
      <input type="text" {...input} placeholder="Name" />
      {meta.touched && meta.error && <span>{meta.error}</span>}
    </div>
  )}
</Field>

<Field name="startDate">
  {({ input, meta }) => (
    <div>
      <label>Start Date</label>
      <input type="date" {...input} placeholder="Start Date" />
      {meta.touched && meta.error && <span>{meta.error}</span>}
    </div>
  )}
</Field>

<Field name="endDate">
  {({ input, meta }) => (
    <div>
      <label>End Date</label>
      <input type="date" {...input} placeholder="End Date" />
      {meta.touched && meta.error && <span>{meta.error}</span>}
    </div>
  )}
</Field>

We're assuming we already have our <Form> wrapper and submit button elsewhere.

So far with this code, all of our fields will update independently.

To tie together our endDate field with the startDate, we'll need a custom onChange method and a way to access the form API ourselves.

Form Hook

Two hooks come in handy here:

  • userForm(): Gives us all access to utility methods for our form. It can be called in any component within the <Form> wrapper.
  • useFormState(): As the name implies, gives us access to the current state, including values and meta data such as what fields have been "touched"

We'll open up our component with these hooks:

import React from 'react';
import {Field, useForm, useFormState} from 'react-final-form';

const FormComponent = () => {
  const formApi = useForm();
  const {values} = useFormState();

  return(
    ...
  )
};

And then use these in a custom onChange handler on our field

<Field name="startDate">
  {({ input, meta }) => (
    <div>
      <label>Start Date</label>
      <input
        type="date"
        {...input}
        placeholder="Start Date"
        // Custom onChange below
        onChange={(e) => {
          const newStartDate = e.currentTarget.valueAsDate;

          // Lets assume I've written a COOL function that takes in the
          // Initial values for startDate and endDate, and calculates a
          // new endDate based on that
          const newValue = calculateNewDateBasedOnNewStartDate(newStartDate, values);

          // Update both values through the formApi
          formApi.change('startDate', newStartDate)
          formApi.change('endDate', newValue)
        }}
      />
      {meta.touched && meta.error && <span>{meta.error}</span>}
    </div>
  )}
</Field>

There you go! The endDate will update alongside the start date from here!