React 17.0.1: Invalid hook call. Hooks can only be called inside of the body of a function component
Asked Answered
I

2

7

Very new react user here. I have been troubleshooting this error for hours now. I previously had this component working as a class, but it seems everyone is migrating towards functional components lately, so I'm trying to convert it. I did an intermediate conversion without using state and it worked fine, but I am having trouble converting username and password to state values.

When I use the following code, I get an error:

Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.

I have read through the linked troubleshooting page multiple times, and tried fixing all three possible causes with no success.

  1. I am using react 17.0.1 and react-dom 17.0.1 in my devDependencies.
  2. I have installed the eslint-plugin-react-hooks rule to identify if I am not using hooks correctly in my functional component.
  3. I have used npm ls react and npm ls react-dom to confirm I do not have duplicate react versions.

LoginForm.jsx

import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
const $ = require('jquery');

const LoginForm = ({ onToken }) => {

    const [username, setUsername] = useState('');
    const [password, setPassword] = useState('');

    const submit = (event) => {
        // DO NOT POST PARAMS
        event.preventDefault();
        // AJAX username and password
        const loginData = {
            username: username,
            password: password
        };
        const options = {
            url: 'api/login',
            type: 'POST',
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            data: JSON.stringify(loginData),
            success: (data) => {
                console.log(data);
                onToken(data.token);
            }
        };

        $.ajax(options);

        console.log('Submitted!');
    };

    return (
        <form onSubmit={submit}>
            <label htmlFor="username">Username:</label><br/>
            <input value={username} onChange={e => setUsername(e.target.value)} type="text" id="username" name="username"/><br/>
            <label htmlFor="password">Password:</label><br/>
            <input value={password} onChange={e => setPassword(e.target.value)} type="password" id="password" name="password"/><br/>
            <input type="submit" value="Login"/>
        </form>
    );
};

LoginForm.propTypes = {
    onToken: PropTypes.func
};

export default LoginForm;

I have created a branch on my project's repository so you can view any other information:

Branch: https://github.com/GibsDev/node-password-manager/tree/login-conversion

Commit: https://github.com/GibsDev/node-password-manager/commit/e0714b16bdbbf339fabf1aa4f6eefacd6d053427

Ishmael answered 31/1, 2021 at 18:18 Comment(2)
where this error happens, it seems your component is alright.Fernandina
It occurs in the browser. When I try to view the page that includes this component. Maybe I am not adding the component correctly? github.com/GibsDev/node-password-manager/blob/login-conversion/…Ishmael
D
7

You're calling this loginForm as if it was a plain function, not as a React component:

const loginForm = LoginForm({
    onToken: token => {
        const url = new URL(window.location.href);
        const returnurl = url.searchParams.get('returnurl');
        if (returnurl) {
            window.location.href = decodeURIComponent(returnurl);
        } else {
            window.location.href = '/';
        }
    }
});

ReactDOM.render(loginForm, domContainer);

As a result, it doesn't get wrapped in React.createElement, so it thinks the hook call is invalid.

Use JSX syntax instead:

const onToken = token => {
        const url = new URL(window.location.href);
        const returnurl = url.searchParams.get('returnurl');
        if (returnurl) {
            window.location.href = decodeURIComponent(returnurl);
        } else {
            window.location.href = '/';
        }
}

ReactDOM.render(<LoginForm onToken={onToken} />, domContainer);
          //    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Dupuy answered 31/1, 2021 at 18:41 Comment(6)
It worked! Is there a javascript equivalent for doing this?Ishmael
You can, but I wouldn't recommend it: ReactDOM.render(React.createElement(LoginForm, { onToken }), domContainer)Dupuy
Ahh, so you would still need to import React in that solution anyway. Thank you so much for the help!Ishmael
I guess react 17 eliminate importing React itself when you only need jsxFernandina
@AmirhosseinEbrahimi Yeah, that's pretty cool: reactjs.org/blog/2020/09/22/… Though it's more obfuscating that the element is being created through React than removing it completelyDupuy
That is quite nice, I just enabled it within babel. Now I just need to figure out how to get ESLint to stop warning me about it. babeljs.io/blog/2020/03/16/…Ishmael
B
1

I got similar error in my repo. And it started appearing when I started bumping react from 16 to 17. Later I realised its due to react16 is being pulled by the different dependency into node_modules and removing that fixed my issue

Biarritz answered 11/2, 2022 at 15:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.