TypeError: Cannot read properties of null (reading 'useState') while testing component with react testing library
Asked Answered
L

3

9

I'm learning testing on react and react components with react testing library and jest. While trying to test a component that works when rendering the component it works with no issue, but when testing it I get the Invalid hook error.

This is the Component.

import PropTypes from "prop-types";
import { useState } from "react";

export const CounterApp = ({ value }) => {
    const [counter, setCounter] = useState(value);

    function handleAdd() {
        setCounter(counter + 1);
    }
    function handleSubtract() {
        if (counter > 0) {
            setCounter(counter - 1);
        }
    }

    function handleReset() {
        setCounter(0);
    }
    return (
        <>
            <h1>CounterApp</h1>
            <h2> {counter} </h2>
            <button onClick={handleAdd}>+1</button>
            <button onClick={handleSubtract}>-1</button>
            <button onClick={handleReset}>Reset</button>
        </>
    );
};

CounterApp.propTypes = {
    value: PropTypes.number.isRequired,
};

This is the test I'm running

import { render } from "@testing-library/react";
import { CounterApp } from "../../src/components/CounterApp";

describe("<CounterApp /> Tests", () => {
    const initialValue = 10;

    test("should match the snapshot", () => {
        const { container } = render(<CounterApp value={initialValue} />);
        expect(container).toMatchSnapshot();
    });
});

And this is the error that I'm getting

 ● <CounterApp /> Tests › should match the snapshot                  
                                                                      
    TypeError: Cannot read properties of null (reading 'useState')    
                                                                      
      3 |                                                             
      4 | export const CounterApp = ({ value }) => {                  
    > 5 |       const [counter, setCounter] = useState(value);        
        |                                             ^
      6 |
      7 |       function handleAdd() {
      8 |               setCounter(counter + 1);

      at useState (node_modules/react/cjs/react.development.js:1622:21)
      at CounterApp (src/components/CounterApp.jsx:5:40)
      at renderWithHooks (../node_modules/react-dom/cjs/react-dom.development.js:16305:18)
      at mountIndeterminateComponent (../node_modules/react-dom/cjs/react-dom.development.js:20074:13)
      at beginWork (../node_modules/react-dom/cjs/react-dom.development.js:21587:16)
      at beginWork$1 (../node_modules/react-dom/cjs/react-dom.development.js:27426:14)
      at performUnitOfWork (../node_modules/react-dom/cjs/react-dom.development.js:26560:12)
      at workLoopSync (../node_modules/react-dom/cjs/react-dom.development.js:26466:5)
      at renderRootSync (../node_modules/react-dom/cjs/react-dom.development.js:26434:7)
      at recoverFromConcurrentError (../node_modules/react-dom/cjs/react-dom.development.js:25850:20)
      at performConcurrentWorkOnRoot (../node_modules/react-dom/cjs/react-dom.development.js:25750:22)
      at flushActQueue (../node_modules/react/cjs/react.development.js:2667:24)
      at act (../node_modules/react/cjs/react.development.js:2582:11) 
      at ../node_modules/@testing-library/react/dist/act-compat.js:63:25
      at renderRoot (../node_modules/@testing-library/react/dist/pure.js:159:26)
      at render (../node_modules/@testing-library/react/dist/pure.js:246:10)
      at Object.<anonymous> (test/components/CounterApp.test.jsx:8:31)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        2.194 s

Any advice will be really appreciate. Thanks in advance!

Lauretta answered 28/1, 2023 at 20:29 Comment(6)
I copy pasted your code into my project (learning to test with RTL myself). For me the test succeeded without problem, so the issue is not in your code, but probably in your configuration. Which version of react are you using, I believe it has to be 16.8.0 or laterBoastful
@Boastful I'm using react 18.2 I'm gonna try working around the configuration and run it againBrume
@DanielDavidJiménezPaz did you ever figure this out? I am trying to bump react to 18.2 and am hitting this exact issue...Delius
@Delius I solved it redoing the configuration, I deleted all the npm packages and dependencies and reinstall and it worked. I couldn't find which was causing the issueBrume
I did figure out the issue. I had a weird mishmash of commonjs and esm going on and somehow react was ending up both required() and imported which are different modules in the runtime and therefore with different state. One had the dispatcher appropriately set, and one didn’t. Moving to vite/vitest and only using esm completely resolved the issue.Delius
In case it helps someone, I ended up here because I was calling jest.resetModules() unnecessarily, which undid some other mocks I had.Carbonaceous
E
3

I ran into this exact error myself because I had installed the testing frameworks in the outer directory of my project rather than the directory containing .babelrc, src/, components/, etc. If you run into this, it seems likely it will be a setup issue with what you have installed and where.

What I ran in my project directory: npm install --save-dev @testing-library/react @testing-library/jest-dom jest jest-environment-jsdom @babel/preset-env @babel/preset-react

Escribe answered 3/1 at 17:3 Comment(0)
B
1

Not specifically for this question, but if you are running into the same error message and by any chance you are instantiating a hook and sending it as a parameter of a component like this:

const myHook = useMyHook();
const container = render(
<Component
    useHook={myHook}
/>);

It will throw the same error, a hook must be instantiated inside a valid React component and not a test file, so you should mock the hook or use the renderHook() method from "@testing-library/react", like this:

const result = renderHook(() => useMyHook());
const container = render(
<Component
    useHook={result.current}
/>);
Bookstall answered 4/7 at 6:30 Comment(0)
A
-3

Perhaps in your state you need to initialize the initial value, which is the default?
For example,
const [counter, setCounter] = useState(0);
or
const [counter, setCounter] = useState(value || 0);

Actaeon answered 28/1, 2023 at 20:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.