最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - React - Error: Invalid hook call. Hooks can only be called inside of the body of a function component. What is wron

programmeradmin4浏览0评论

To give a little background:

I'm trying to build a Login Form Page function that detects if the user is already logged. If they are logged in, then a button will appear prompting the user to log out. Like so: {user ? userLoggedInAlert() : SignIn()} If user is True, then the button appears, if user is false, then they receive the username and password form for login authentication.

I have nailed down all the functionality. However, when I press on button to logout the user out, I receive this error:

×
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  for tips about how to debug and fix this problem.

Here is my code, I think there is something wrong with the const userLoggedInAlert

import React from 'react';
import { useSelector } from 'react-redux';
import { selectUser } from '../../features/userSlice';
import SignIn from '../auth/Login';
import Container from '@material-ui/core/Container';
import { Button } from '@material-ui/core';
import { logout } from "../../features/userSlice";
import { NavLink } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import LogOut from '../auth/Logout';



function LoginUser()
{
    const user = useSelector(selectUser);

    const dispatch = useDispatch();

    const handleLogout = (e) =>
    {
        if(e) e.preventDefault();

        dispatch(logout());
    };

    const userLoggedInAlert = () =>
    {
        return <Container>
            <span>
                You are already logged in!
            </span>
            <Button
                component={NavLink}
                variant="contained"
                color="primary"
                to="/logout" 
                onClick={function(){ LogOut(); handleLogout()}}
            >
                Do you want to logout?
            </Button>

              </Container>
        };

    return (
        <Container>
            {user ? userLoggedInAlert() : SignIn()}
        </Container>
    );
}
export default LoginUser;
  • SignIn() works just fine, so does my useDispatch, and other components necessary to create the code above. I'm pretty sure I just messed up the syntax somewhere in the code above?

Here is my code to LogOut(), note that I can logout just fine through other means using this exact same component.

import React, { useEffect } from 'react';
import axiosInstance from '../../axios';
import { useHistory } from 'react-router-dom';

export default function LogOut() {
    
    const history = useHistory();


    useEffect(() => {
        const response = axiosInstance.post('user/logout/blacklist/', {
            refresh_token: localStorage.getItem('refresh_token'),
        });
        localStorage.removeItem('access_token');
        localStorage.removeItem('refresh_token');
        axiosInstance.defaults.headers['Authorization'] = null;
        history.push('/login');
    });
    return <div>Logout</div>;
};

Thank you for any help, I'm stuck here.

EDIT:

  "dependencies": {
    "@material-ui/core": "^4.11.2",
    "@material-ui/icons": "^4.11.2",
    "@material-ui/lab": "^4.0.0-alpha.57",
    "@reduxjs/toolkit": "^1.5.0",
    "@testing-library/jest-dom": "^5.11.6",
    "@testing-library/react": "^11.2.2",
    "@testing-library/user-event": "^12.6.0",
    "axios": "^0.21.1",
    "chart.js": "^2.9.4",
    "react": "^17.0.1",
    "react-chartjs-2": "^2.11.1",
    "react-dom": "^17.0.1",
    "react-facebook-login": "^4.1.1",
    "react-hook-form": "^6.14.2",
    "react-redux": "^7.2.2",
    "react-router-dom": "^5.2.0",
    "react-scripts": "4.0.1",
    "redux": "^4.0.5",
    "redux-persist": "^6.0.0",
    "web-vitals": "^0.2.4"
  },

EDIT EDIT: I'm struggling to implement the recommended changes, I'm now getting some more errors that are making me confused:

Uncaught Error: Objects are not valid as a React child (found: object with keys {logoutUser}). If you meant to render a collection of children, use an array instead.

The above error occurred in the <useLogOut> component.

Here is how I'm implementing: LoginPortal.js

import React from 'react';
import { useSelector } from 'react-redux';
import { selectUser } from '../../features/userSlice';
import SignIn from '../auth/Login';
import Container from '@material-ui/core/Container';
import { Button } from '@material-ui/core';
import { logout } from "../../features/userSlice";
import { NavLink } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import useLogOut from '../auth/Logout';

function LoginUser()
{
    const user = useSelector(selectUser);
    // use hook at component body extracting logoutUser method
    const { logoutUser } = useLogOut();

    const dispatch = useDispatch();

    const handleLogout = (e) =>
    {
        if(e) e.preventDefault();

        dispatch(logout());
    };

    const UserLoggedInAlert = () =>
    {
        return <Container>
            <span>
                You are already logged in!
            </span>
            <Button
                component={NavLink}
                variant="contained"
                color="primary"
                to="/logout"
                // here now you can save logout user since no hooks are being called
                onClick={function(){ logoutUser(); handleLogout()}}
            >
                Do you want to logout?
            </Button>

              </Container>
        };

    return (
        <Container>
            {user ? <UserLoggedInAlert/> : <SignIn/> }
        </Container>
    );
}
export default LoginUser;

Logout.js component:

import axiosInstance from '../../axios';
import { useHistory } from 'react-router-dom';

export default function useLogOut() {
    
    const history = useHistory();
  
    // we don't useEffect here, we are only interested in function logoutUser
  
    const logoutUser = () => {
        const response = axiosInstance.post('user/logout/blacklist/', {
            refresh_token: localStorage.getItem('refresh_token'),
        });
        localStorage.removeItem('access_token');
        localStorage.removeItem('refresh_token');
        axiosInstance.defaults.headers['Authorization'] = null;
        history.push('/login');
      }
  
    return { logoutUser }
  };

My index.js:

import useLogOut from './components/auth/Logout';


const routing = (
    <Router>
                <Route path="/logout" component={useLogOut} />
    </Router>
);

ReactDOM.render(routing, document.getElementById('root'));

Would this be the correct path for my index router now that Ive changed it?

New errors:

Error: Objects are not valid as a React child (found: object with keys {logoutUser}). If you meant to render a collection of children, use an array instead.

To give a little background:

I'm trying to build a Login Form Page function that detects if the user is already logged. If they are logged in, then a button will appear prompting the user to log out. Like so: {user ? userLoggedInAlert() : SignIn()} If user is True, then the button appears, if user is false, then they receive the username and password form for login authentication.

I have nailed down all the functionality. However, when I press on button to logout the user out, I receive this error:

×
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.

Here is my code, I think there is something wrong with the const userLoggedInAlert

import React from 'react';
import { useSelector } from 'react-redux';
import { selectUser } from '../../features/userSlice';
import SignIn from '../auth/Login';
import Container from '@material-ui/core/Container';
import { Button } from '@material-ui/core';
import { logout } from "../../features/userSlice";
import { NavLink } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import LogOut from '../auth/Logout';



function LoginUser()
{
    const user = useSelector(selectUser);

    const dispatch = useDispatch();

    const handleLogout = (e) =>
    {
        if(e) e.preventDefault();

        dispatch(logout());
    };

    const userLoggedInAlert = () =>
    {
        return <Container>
            <span>
                You are already logged in!
            </span>
            <Button
                component={NavLink}
                variant="contained"
                color="primary"
                to="/logout" 
                onClick={function(){ LogOut(); handleLogout()}}
            >
                Do you want to logout?
            </Button>

              </Container>
        };

    return (
        <Container>
            {user ? userLoggedInAlert() : SignIn()}
        </Container>
    );
}
export default LoginUser;
  • SignIn() works just fine, so does my useDispatch, and other components necessary to create the code above. I'm pretty sure I just messed up the syntax somewhere in the code above?

Here is my code to LogOut(), note that I can logout just fine through other means using this exact same component.

import React, { useEffect } from 'react';
import axiosInstance from '../../axios';
import { useHistory } from 'react-router-dom';

export default function LogOut() {
    
    const history = useHistory();


    useEffect(() => {
        const response = axiosInstance.post('user/logout/blacklist/', {
            refresh_token: localStorage.getItem('refresh_token'),
        });
        localStorage.removeItem('access_token');
        localStorage.removeItem('refresh_token');
        axiosInstance.defaults.headers['Authorization'] = null;
        history.push('/login');
    });
    return <div>Logout</div>;
};

Thank you for any help, I'm stuck here.

EDIT:

  "dependencies": {
    "@material-ui/core": "^4.11.2",
    "@material-ui/icons": "^4.11.2",
    "@material-ui/lab": "^4.0.0-alpha.57",
    "@reduxjs/toolkit": "^1.5.0",
    "@testing-library/jest-dom": "^5.11.6",
    "@testing-library/react": "^11.2.2",
    "@testing-library/user-event": "^12.6.0",
    "axios": "^0.21.1",
    "chart.js": "^2.9.4",
    "react": "^17.0.1",
    "react-chartjs-2": "^2.11.1",
    "react-dom": "^17.0.1",
    "react-facebook-login": "^4.1.1",
    "react-hook-form": "^6.14.2",
    "react-redux": "^7.2.2",
    "react-router-dom": "^5.2.0",
    "react-scripts": "4.0.1",
    "redux": "^4.0.5",
    "redux-persist": "^6.0.0",
    "web-vitals": "^0.2.4"
  },

EDIT EDIT: I'm struggling to implement the recommended changes, I'm now getting some more errors that are making me confused:

Uncaught Error: Objects are not valid as a React child (found: object with keys {logoutUser}). If you meant to render a collection of children, use an array instead.

The above error occurred in the <useLogOut> component.

Here is how I'm implementing: LoginPortal.js

import React from 'react';
import { useSelector } from 'react-redux';
import { selectUser } from '../../features/userSlice';
import SignIn from '../auth/Login';
import Container from '@material-ui/core/Container';
import { Button } from '@material-ui/core';
import { logout } from "../../features/userSlice";
import { NavLink } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import useLogOut from '../auth/Logout';

function LoginUser()
{
    const user = useSelector(selectUser);
    // use hook at component body extracting logoutUser method
    const { logoutUser } = useLogOut();

    const dispatch = useDispatch();

    const handleLogout = (e) =>
    {
        if(e) e.preventDefault();

        dispatch(logout());
    };

    const UserLoggedInAlert = () =>
    {
        return <Container>
            <span>
                You are already logged in!
            </span>
            <Button
                component={NavLink}
                variant="contained"
                color="primary"
                to="/logout"
                // here now you can save logout user since no hooks are being called
                onClick={function(){ logoutUser(); handleLogout()}}
            >
                Do you want to logout?
            </Button>

              </Container>
        };

    return (
        <Container>
            {user ? <UserLoggedInAlert/> : <SignIn/> }
        </Container>
    );
}
export default LoginUser;

Logout.js component:

import axiosInstance from '../../axios';
import { useHistory } from 'react-router-dom';

export default function useLogOut() {
    
    const history = useHistory();
  
    // we don't useEffect here, we are only interested in function logoutUser
  
    const logoutUser = () => {
        const response = axiosInstance.post('user/logout/blacklist/', {
            refresh_token: localStorage.getItem('refresh_token'),
        });
        localStorage.removeItem('access_token');
        localStorage.removeItem('refresh_token');
        axiosInstance.defaults.headers['Authorization'] = null;
        history.push('/login');
      }
  
    return { logoutUser }
  };

My index.js:

import useLogOut from './components/auth/Logout';


const routing = (
    <Router>
                <Route path="/logout" component={useLogOut} />
    </Router>
);

ReactDOM.render(routing, document.getElementById('root'));

Would this be the correct path for my index router now that Ive changed it?

New errors:

Error: Objects are not valid as a React child (found: object with keys {logoutUser}). If you meant to render a collection of children, use an array instead.
Share Improve this question edited Feb 5, 2021 at 3:29 yung peso asked Feb 5, 2021 at 1:55 yung pesoyung peso 1,7668 gold badges37 silver badges84 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 12

when you call Logout at onClick you are calling hooks from your function Logout outside of a component body, what's is not allowed.

You can abstract your Logout logic to a custom hook where it returns the logoutUser function:

export default function useLogOut() {
    
  const history = useHistory();

  // we don't useEffect here, we are only interested in function logoutUser

  const logoutUser = () => {
      const response = axiosInstance.post('user/logout/blacklist/', {
          refresh_token: localStorage.getItem('refresh_token'),
      });
      localStorage.removeItem('access_token');
      localStorage.removeItem('refresh_token');
      axiosInstance.defaults.headers['Authorization'] = null;
      history.push('/login');
    }

  return { logoutUser }
};

then you consume your custom hook at your LoginUser component body, extracting the logoutUser method:

function LoginUser()
{
    const user = useSelector(selectUser);
    // use hook at component body extracting logoutUser method
    const { logoutUser } = useLogOut();

    const dispatch = useDispatch();

    const handleLogout = (e) =>
    {
        if(e) e.preventDefault();

        dispatch(logout());
    };

    const UserLoggedInAlert = () =>
    {
        return <Container>
            <span>
                You are already logged in!
            </span>
            <Button
                component={NavLink}
                variant="contained"
                color="primary"
                to="/logout"
                // here now you can safely logout user since no hooks are being called
                onClick={function(){  handleLogout(); logoutUser()}}
            >
                Do you want to logout?
            </Button>

              </Container>
        };

    return (
        <Container>
            {user ? <UserLoggedInAlert/> : <SignIn/> }
        </Container>
    );
}
export default LoginUser;

you can take a step ahead and simplify your LogOut component:

export default function LogOut() {
    
  const { logoutUser } = useLogOut();

  useEffect(() => {
    logoutUser()
  });
  return <div>Logout</div>;
};

Edit

for simplicity at your Logout.js do as:

export const useLogOut = () => {
    
  const history = useHistory();

  // we don't useEffect here, we are only interested in function logoutUser

  const logoutUser = () => {
      const response = axiosInstance.post('user/logout/blacklist/', {
          refresh_token: localStorage.getItem('refresh_token'),
      });
      localStorage.removeItem('access_token');
      localStorage.removeItem('refresh_token');
      axiosInstance.defaults.headers['Authorization'] = null;
      history.push('/login');
    }

  return { logoutUser }
};

export default function LogOut() {

  const { logoutUser } = useLogOut();

  useEffect(() => {
    logoutUser()
  });
  return <div>Logout</div>;
};

at your your Login you import the hook with curly brackets:

import { useLogOut } from '../auth/Logout';

and at your index.js without the curly brackets you import the component:

import LogOut from './components/auth/Logout';


const routing = (
    <Router>
                <Route path="/logout" component={LogOut} />
    </Router>
);

Components are basically function references, and you are invoking them, so instead of doing SignIn(), try <SignIn />

edit: and make userLoggedInAlert start with upper case, then all together:

{user ? <UserLoggedInAlert /> : <SignIn />}

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论