Another mini lesson regarding coding, this time about Formik, a form-building library for React.

The application I work on uses Formik for its forms.

Formik is the world's most popular open source form library for React and React Native. Formik takes care of the repetitive and annoying stuff—keeping track of values/errors/visited fields, orchestrating validation, and handling submission—so you don't have to.

Situation:

As a minor feature, our app allows users to specify up to two different location preferences – places where they'd be willing to work. You can specify specific cities, states, countries, 'remote' or just 'anywhere'.

So the app allows you to add either one or two of these location preferences, but up until now, there was no option to remove a location preference. You could only alter them.

So that was my task: to make it possible to remove a location preference. Seems straightforward enough, right? I thought so too, but when I looked at the code, I couldn't really make sense of it.


  let [, { value: firstLocation }, { setValue: setFirstLocation }] = useField(
    firstLocationPath
  );
  let [, { value: secondLocation }, { setValue: setSecondLocation }] = useField(
    secondLocationPath
  );
  

What I did understand was that the state of the locations was tied to their respective fields. But where was the parent, the array of locations? How do I access and alter it? Why is there no useState() hook with a setter function I can use?

Of course, I could use the setter function on the location to be deleted, and just set it to null, but then we'd end up with an array with two elements, one of which is null.


[
    { 
        type: LocationType.city,
        city: 'San Francisco',
        state: 'CA',
        country: 'United States'
    },
    null
]

We could write code to handle that, but it's far from elegant. So what's going on here?

Lesson:

Formik uses its own state and ways to update it. When you open a form, Formik clones the state of the variables you're using (in this case the locations) into its own form state. That means that this state can now be altered and updated independently, without it directly affecting the rest of the DOM.

When the form gets submitted, the form's state gets sent to the server through an update mutation, the server takes care of updates in the database and sends updated fields back to React, after which React's state gets updated. This way, the form's state and the state in the app elsewhere never conflict, and there's always a single source of truth.

To access the form's state, you can use Formik's useField() hook and specify the path to the particular field you're trying to target – in my case the locations array. You can deconstruct helper functions from a useField() hook just like normal, to help you change a field's values.

So in my case, getting access to the locations parent array looked like this:


  let [, , { setValue: setLocationArray }] = useField(
    locationsPath
  );
  

It's worth noting that there is also a FieldArray component in Formik that is intended for dealing with arrays like this, specifically. It comes with its own helper functions like push and remove which can be very handy.

In our case though, since we're dealing with an array that will only ever have two elements maximum, I elected to keep it simple and stick with a standard useField() hook.