
The 16.8.0 version release of React meant a stable release of the React Hooks feature. React Hooks was introduced in 2018 and got favorable reviews from the React ecosystem. It’s essentially a way to create components with features, like state, without the need for class components.
What is React Hooks?
The Hooks feature is a welcome change as it solves many of the problems React devs have faced over the years. One of those problems is the case of React not having support for reusable state logic between class
components. This can sometimes lead to huge components, duplicated logic in the constructor and lifecycle methods.
Inevitably, this forces us to use some complex patterns such as render props and higher order components and that can lead to complex codebases.
Hooks aim to solve all of these by enabling you to write reusable components with access to state, lifecycle methods, refs e.t.c.
The various types of React Hooks
Below are some of the major Hooks that will be used generally in your React apps:
- useState : allows us to write pure functions with state in them
- useEffect : lets us perform side effects. Side effects can be API calls, updating the DOM, subscribing to event listeners
- useContext : allows us to write pure functions with context in them
- useRef : allows us to write pure functions that return a mutable
ref
object
The other Hooks that can be used in your React apps for specific edge cases include:
- useReducer : an alternative to
useState
. Accepts a reducer of type(state, action) => newState
, and returns the current state paired with adispatch
method. It is usually preferable touseState
when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one - useMemo : useMemo is used to return a memoized value
- useCallback : this hook is used to return a memoized callback
- useImperativeMethods : useImperativeMethods customizes the instance value that is exposed to parent components when using
ref
- useMutationEffects : the useMutationEffect is similar to the useEffect Hook in the sense that it allows you to perform DOM mutations
- useLayoutEffect : the useLayoutEffect hook is used to read layout from the DOM and synchronously re-render
How to build a React app using React Hooks
Before we go on to see how to write tests for React Hooks, let’s see how to build a React app using Hooks. We’ll be building an app that shows the 2018 F1 races and the winners for each year.
The whole app can be seen and interacted with at CodeSandbox.
React Hooks Example
React Hooks Example by yomete using react, react-awesome-styled-grid, react-dom, react-scripts, styled-components
In the app above, we’re using the useState
and useEffect
Hooks. If you navigate to the index.js
file, in the App
function, you’ll see an instance where useState
is used.
// Set the list of races to an empty array let [races, setRaces] = useState([]); // Set the winner for a particular year let [winner, setWinner] = useState("");
useState
returns a pair of values, that is the current state value and a function that lets you update it. It can be initialized with any type of value (string, array e.t.c) as opposed to state in classes where it had to be an object.
The other Hook that’s in use here is the useEffect
Hook. The useEffect
Hook adds the ability to perform side effects from a function component. It essentially allows you to perform operations you’d usually carry out in the componentDidMount
, componentDidUpdate
, and componentWillUnmount
lifecycles.
// On initial render of component, fetch data from API. useEffect(() => { fetch(`https://ergast.com/api/f1/2018/results/1.json`) .then(response => response.json()) .then(data => { setRaces(data.MRData.RaceTable.Races); }); fetch(`https://ergast.com/api/f1/2018/driverStandings.json`) .then(response => response.json()) .then(data => { let raceWinner = data.MRData.StandingsTable.StandingsLists[0].DriverStandings[0].Driver.familyName + " " + data.MRData.StandingsTable.StandingsLists[0].DriverStandings[0].Driver.givenName; setWinner(raceWinner); }); }, []);
In the app, we’re using the useEffect
Hook to make API calls and fetch the F1 races data and then using the setRaces
and setWinner
functions to set their respective values into the state.
That’s just an example of how Hooks can be used in combination to build an app. We use the useEffect
Hook to fetch data from some source and the useState
to set the data gotten into a state.
Testing React Hooks with Jest or Enzyme
What are Jest and Enzyme?
Jest and Enzyme are tools used for testing React apps. Jest is a JavaScript testing framework used to test JavaScript apps and Enzyme is a JavaScript testing utility for React that makes it easier to assert, manipulate, and traverse your React Components’ output.
They are probably the go-to testing tools for React, so we’ll see if they can be used to test React Hooks. To do that, I’ve created an app on CodeSandbox that we’ll use for our test suites. You can follow along by forking the app on CodeSandbox.
Navigate to the __tests__
folder to see the hooktest.js
file that contains the test suite.
import React from "react"; import ReactDOM from "react-dom"; import App from "../index"; it("renders without crashing", () => { const div = document.createElement("div"); ReactDOM.render(<App />, div); ReactDOM.unmountComponentAtNode(div); });
We’ll first write a test to see if the app renders without crashing.
Next up, we’ll try using the Enzyme testing library to test React Hooks. To use Enzyme we’ll need to install the following dependencies to the CodeSandbox app:
Testing React Hooks
Testing React Hooks by yomete using enzyme, enzyme-adapter-react-16, react, react-dom, react-scripts
Navigate to the __tests__
folder to see the hooktest.js
file that contains the test suite.
In the hooktest.js
file, an additional test block is added. We are testing using the shallow
method imported from Enzyme. The shallow method or rendering is used to test components as a unit. It is a simulated render of a component tree that does not require a DOM.
We get the error below when we try to test using Enzyme.

Hooks can only be called inside the body of a function component. (https://fb.me/react-invalid-hook-call)
The error above means that Hooks are not yet supported in Enzyme as seen in this issue here.
As a result, we cannot use Enzyme to carry out component tests for React Hooks. So what can be used?
Introducing react-testing-library
react-testing-library is a very light-weight solution for testing React components. It extends upon react-dom
and react-dom/test-utils
to provide light utility functions. It encourages you to write tests that closely resemble how your react components are used.
Let’s see an example of writing tests for Hooks using react-testing-library.
Testing React Hooks
Testing React Hooks by yomete using enzyme, enzyme-adapter-react-16, react, react-dom, react-scripts
In the app above, three types of Hooks are in use, useState
, useEffect
, useRef
, and we’ll be writing tests for all of them.
In addition to the useState
example in which we’re incrementing and decrementing a count, we’ve also added two more examples.
For the useRef
Hook implementation, we’re essentially creating a ref
instance using useRef
and setting it to an input field and that would mean that the input’s value can now be accessible through the ref.
The useEffect
Hook implementation is essentially setting the value of the name
state to the localStorage
.
Let’s go ahead and write tests for all of the implementation above. We’ll be writing the test for the following:
- The initial
count
state is 0 - The
increment
anddecrement
buttons work - Submitting a name via the input field changes the value of the
name
state - The
name
state is saved in the localStorage
Navigate to the __tests__
folder to see the hooktest.js
file that contains the test suite and the import line of code below.
// hooktest.js import { render, fireEvent, getByTestId} from "react-testing-library";
render
— this will help render our component. It renders into a container which is appended todocument.body
getByTestId
— this fetches a DOM element bydata-testid
fireEvent
— this is used to “fire” DOM events. It attaches an event handler on thedocument
and handles some DOM events via event delegation e.g. clicking on a button- rerender — this is used to simulate a page reload
Next, add the test suite below in the hooktest.js
file.
// hooktest.js it("App loads with initial state of 0", () => { const { container } = render(<App />); const countValue = getByTestId(container, "countvalue"); expect(countValue.textContent).toBe("0"); });
The test checks that if the initial count state is set to 0 by first fetching the element with the getByTestId helper. It then checks if the content is 0 using the expect()
and toBe()
functions.

Next, we’ll write the test to see if the increment and decrement buttons work.
// hooktest.js it("Increment and decrement buttons work", () => { const { container } = render(<App />); const countValue = getByTestId(container, "countvalue"); const increment = getByTestId(container, "incrementButton"); const decrement = getByTestId(container, "decrementButton"); expect(countValue.textContent).toBe("0"); fireEvent.click(increment); expect(countValue.textContent).toBe("1"); fireEvent.click(decrement); expect(countValue.textContent).toBe("0"); });
In the test above, The test checks that if the onButton
is clicked on, the state is set to 1 and when the offButton
is clicked on, the state is set to 1.

For the next step, we’ll write a test to assert if submitting a name via the input field actually changes the value of the name
state and that it’s successfully saved to the localStorage
.
// hooktest.js it("Submitting a name via the input field changes the name state value", () => { const { container, rerender } = render(<App />); const nameValue = getByTestId(container, "namevalue"); const inputName = getByTestId(container, "inputName"); const submitButton = getByTestId(container, "submitRefButton"); const newName = "Ben"; fireEvent.change(inputName, { target: { value: newName } }); fireEvent.click(submitButton); expect(nameValue.textContent).toEqual(newName); rerender(<App />); expect(window.localStorage.getItem("name")).toBe(newName); });
In the test assertion above, the fireEvent.change
method is used to enter a value into the input
field, after which the submit button is clicked on.
The test then checks if the value of the ref after the button was clicked is equal to the newName
. Finally, using the rerender
method, a reload of the app is simulated and there’s a check to see if name set previously was stored to the localStorage.

Implementing Redux in your app? Track Redux state and actions with LogRocket
Debugging React applications can be difficult, especially when there is complex state. If you’re interested in monitoring and tracking Redux state for all of your users in production, try LogRocket. https://logrocket.com/signup/
LogRocket is like a DVR for web apps, recording literally everything that happens on your site. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.
The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.
Modernize how you debug your React apps – Start monitoring for free.
Conclusion
In this article, we’ve seen how to write tests for React Hooks and React components using the react-testing-library. We also went through a short primer on how to use React Hooks. Want to learn more? Check out this post on how to avoid common mistakes with React hooks.
If you have any questions or comments, you can share them below.
Unfortunately, the tests at https://codesandbox.io/s/rqj0lymyn do not run due to
`Invariant Violation: Target container is not a DOM element.`
Its not about testing react hooks, but react component using hooks. :disappointed:
I was able to test a react component using hooks with enzyme.
good article, concise summary on the basics & tooling for react hook-component testing. thanks! how do you stub HTTP requests? for example, is there a way to intercept the fetch requests going into the first Formula 1 app on mount use effect hook?
Thank you for the aha moment!
It is just for testing simple react hooks function that you can get from anywhere. You haven’t added Testing for async hook component. :disappointed:
Good read! 👍
Good demonstrative but your title is misguiding.
Nice Article! Can you please tell if there is any way to override the default value of the useState(0). For example if I initially want to render the counter using the value 1 instead of 0?
How to test React hooks with Jest and Enzyme? Use react-testing-library instead. Really?