Using React Hook Form in component tests

Adam J. Arling
Sep 27 · 6 min read
React Hook Form screenshot
React Hook Form screenshot

React Hook Form is an incredibly useful package for building out simple to complex web forms. This article illustrates our team’s approach to organizing and testing nested form components, using React Hook Form’s <FormProvider /> and useFormContext() hook and then testing form components with Testing Library.

Standard React Hook Form setup

When our forms were small and being prototyped, it was reasonable to initialize React Hook Form in the standard way according to its docs.

Organizing a complex form

As data gathering applications grow, so might the amount of deeply nested child components within each form. Creating nested components is useful to organize form content, reuse code, maintaining accessibility, and reinforce a consistent styling across an application.

Here’s a generic example of how some of our forms are set up. Our repository application contains metadata forms which have 30–50 elements, from simple inputs, to typeahead drop downs, to field array (multi-valued) inputs, and more.

We organize complex, singular forms into sections.

A section component:

A nested component:

And eventually a “leaf-level”, child component where we wire up React Hook Form to the form element.

The initial pattern of drilling down React Hook Form methods as props to every child component in a component stack, got copied over and over again and we duplicated this inefficient pattern because well, it worked but didn’t feel quite right.

useFormContext to the rescue

Recently we transitioned our React Hook Form implementations and child components to use useFormContext. Since we’re gravitating towards using our own component libraries and looking for a consistent solution, now we set up our forms with Context:

This approach flows with React Context/Provider patterns, and any child component in the ancestry tree can grab React Hook Form Context if it needs it. Mid-level components which don’t care about register or error are set free and liberated from baggage props.

So what if you have multiple forms in your application? With React Hook Form Context, whichever form a component lives in, is the form data the component receives via the hook. This also sets up components to be more easily tested by passing whatever form context one wants, into each test.

Testing

Ok, now we have way less code in our components. Check out this PR. Whoa, much better. Let’s move onto testing React Hook Form useContext() components with Testing Library.

This renderWithReactHookForm helper function acts the same way other Testing Library recipes work, by returning what Testing Library’s render() function would return. For example here’s Testing Library’s recipe for wrapping with React Router.

A sample test using renderWithReactHookForm may look like:

In our test above, we wrap the component we’re testing with React Hook Form’s <FormProvider /> and can initialize the form with some default values.

Why would this help?

Rendering with default values

Say you have a form that collects a list of values, but has starting values. (We also have a decent amount of complex form implementations which make use of React Hook Form’s useFieldArray hook).

So the form data could look like this:

Related URL form field array
Related URL form field array

The <UIFormRelatedURL /> form component displays a list of existing values fetched from the API which a user could remove. The user is also free to add as many additional values as they wish. In tests, we inject default values into React Hook Form, the same way the code actually does. So we can test that our component actually displays the proper starting values.

Combining <FormProvider /> with other Providers

Say you use other tools in your application like GraphQL w/ Apollo Client, or React Router and your application looks something like this:

If you are testing a component which gets wrapped in other testing Providers like Apollo Client, React Router, ElasticSearch, etc. we can re-purpose the renderWithReactHookForm pattern as a Higher Order Component which returns a regular Component instead of React Testing Library’s render() function.

And we’d use it as follows:

Extending renderWithReactHookForm()

Now that we can set up individual form context when testing components, we could also extend renderWithReactHookForm to test how a component responds to certain form context values, without submitting the form, which is not possible when testing a deeply nested component which doesn’t render the <form /> element or submit button.

Note: I’m not 100% sure this is a good idea or pattern, but it allows one to at the very least to test nested component form validation and how the UI should respond to bad form data. Maybe you can take this idea and refine it for your use cases or make it better somehow… just experimenting.

On line #18 you’ll notice toPassBack which is an array of React Hook Form methods, for example, setError.

On line #24–26 we’re adding the methods to our helper object, reactHookFormMethods.

Then on line #32 we’re including an additional object, reactHookFormMethods which gets added to what Testing Library’s render() function returns (alongside methods like getByTestId, etc).

Here’s a rough example of how it might be used:

Conclusion

Maybe you’ll find this helper wrapper function helpful in some manner. React Hook Form and Testing Library are top React packages which developers are building a lot of stuff on, so it’s nice to see how to make testing easier. Any thoughts/comments/opinions are more than welcome.

If you’d like to see the example code within the context of an open-source Elixir/React application, here’s a link to the Github repo:

Image for post
Image for post
Subscribe to Decoded, our official YouTube channel!

JavaScript In Plain English

New JavaScript + Web Development articles every day.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store