反应 - 错误:无效的挂钩调用。钩子只能在函数组件的主体内部调用。我的语法有什么问题吗?

woo*_*oof 8 javascript reactjs material-ui

提供一些背景知识:

\n

我正在尝试构建一个登录表单页面函数来检测用户是否已经登录。如果他们已登录,则会出现一个按钮,提示用户注销。就像这样: {user ? userLoggedInAlert() : SignIn()}如果user为 True,则出现该按钮,如果user为 false,则他们会收到用于登录身份验证的用户名和密码表单。

\n

我已经确定了所有功能。\n但是,当我按下按钮注销用户时,我收到此错误:

\n
\xc3\x97\nError: 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:\n1. You might have mismatching versions of React and the renderer (such as React DOM)\n2. You might be breaking the Rules of Hooks\n3. You might have more than one copy of React in the same app\nSee https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.\n
Run Code Online (Sandbox Code Playgroud)\n

这是我的代码,我认为有问题const userLoggedInAlert

\n
import React from 'react';\nimport { useSelector } from 'react-redux';\nimport { selectUser } from '../../features/userSlice';\nimport SignIn from '../auth/Login';\nimport Container from '@material-ui/core/Container';\nimport { Button } from '@material-ui/core';\nimport { logout } from "../../features/userSlice";\nimport { NavLink } from 'react-router-dom';\nimport { useDispatch } from 'react-redux';\nimport LogOut from '../auth/Logout';\n\n\n\nfunction LoginUser()\n{\n    const user = useSelector(selectUser);\n\n    const dispatch = useDispatch();\n\n    const handleLogout = (e) =>\n    {\n        if(e) e.preventDefault();\n\n        dispatch(logout());\n    };\n\n    const userLoggedInAlert = () =>\n    {\n        return <Container>\n            <span>\n                You are already logged in!\n            </span>\n            <Button\n                component={NavLink}\n                variant="contained"\n                color="primary"\n                to="/logout" \n                onClick={function(){ LogOut(); handleLogout()}}\n            >\n                Do you want to logout?\n            </Button>\n\n              </Container>\n        };\n\n    return (\n        <Container>\n            {user ? userLoggedInAlert() : SignIn()}\n        </Container>\n    );\n}\nexport default LoginUser;\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  • SignIn()工作得很好,我的useDispatch和创建上面代码所需的其他组件也工作得很好。我很确定我只是搞乱了上面代码中某处的语法?
  • \n
\n

这是我的代码LogOut(),请注意,我可以使用这个完全相同的组件通过其他方式注销。

\n
import React, { useEffect } from 'react';\nimport axiosInstance from '../../axios';\nimport { useHistory } from 'react-router-dom';\n\nexport default function LogOut() {\n    \n    const history = useHistory();\n\n\n    useEffect(() => {\n        const response = axiosInstance.post('user/logout/blacklist/', {\n            refresh_token: localStorage.getItem('refresh_token'),\n        });\n        localStorage.removeItem('access_token');\n        localStorage.removeItem('refresh_token');\n        axiosInstance.defaults.headers['Authorization'] = null;\n        history.push('/login');\n    });\n    return <div>Logout</div>;\n};\n
Run Code Online (Sandbox Code Playgroud)\n

谢谢你的帮助,我被困在这里了。

\n

编辑:

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

编辑编辑:\n我正在努力实施建议的更改,我现在收到更多错误,这些错误让我感到困惑:

\n
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.\n
Run Code Online (Sandbox Code Playgroud)\n

上述错误发生在<useLogOut>组件中。

\n

这是我的实现方式:\nLoginPortal.js

\n
import React from 'react';\nimport { useSelector } from 'react-redux';\nimport { selectUser } from '../../features/userSlice';\nimport SignIn from '../auth/Login';\nimport Container from '@material-ui/core/Container';\nimport { Button } from '@material-ui/core';\nimport { logout } from "../../features/userSlice";\nimport { NavLink } from 'react-router-dom';\nimport { useDispatch } from 'react-redux';\nimport useLogOut from '../auth/Logout';\n\nfunction LoginUser()\n{\n    const user = useSelector(selectUser);\n    // use hook at component body extracting logoutUser method\n    const { logoutUser } = useLogOut();\n\n    const dispatch = useDispatch();\n\n    const handleLogout = (e) =>\n    {\n        if(e) e.preventDefault();\n\n        dispatch(logout());\n    };\n\n    const UserLoggedInAlert = () =>\n    {\n        return <Container>\n            <span>\n                You are already logged in!\n            </span>\n            <Button\n                component={NavLink}\n                variant="contained"\n                color="primary"\n                to="/logout"\n                // here now you can save logout user since no hooks are being called\n                onClick={function(){ logoutUser(); handleLogout()}}\n            >\n                Do you want to logout?\n            </Button>\n\n              </Container>\n        };\n\n    return (\n        <Container>\n            {user ? <UserLoggedInAlert/> : <SignIn/> }\n        </Container>\n    );\n}\nexport default LoginUser;\n
Run Code Online (Sandbox Code Playgroud)\n

Logout.js成分:

\n
import axiosInstance from '../../axios';\nimport { useHistory } from 'react-router-dom';\n\nexport default function useLogOut() {\n    \n    const history = useHistory();\n  \n    // we don't useEffect here, we are only interested in function logoutUser\n  \n    const logoutUser = () => {\n        const response = axiosInstance.post('user/logout/blacklist/', {\n            refresh_token: localStorage.getItem('refresh_token'),\n        });\n        localStorage.removeItem('access_token');\n        localStorage.removeItem('refresh_token');\n        axiosInstance.defaults.headers['Authorization'] = null;\n        history.push('/login');\n      }\n  \n    return { logoutUser }\n  };\n
Run Code Online (Sandbox Code Playgroud)\n

我的index.js:

\n
import useLogOut from './components/auth/Logout';\n\n\nconst routing = (\n    <Router>\n                <Route path="/logout" component={useLogOut} />\n    </Router>\n);\n\nReactDOM.render(routing, document.getElementById('root'));\n
Run Code Online (Sandbox Code Playgroud)\n

既然我已经更改了索引路由器,这是否是正确的路径?

\n

新错误:

\n
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.\n
Run Code Online (Sandbox Code Playgroud)\n

buz*_*tto 12

当您调用Logoutat时onClick,您正在从组件主体之外的函数调用挂钩Logout,这是不允许的。

您可以将注销逻辑抽象为自定义挂钩,并在其中返回函数logoutUser

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 }
};
Run Code Online (Sandbox Code Playgroud)

然后您在组件主体上使用自定义挂钩LoginUser,提取logoutUser方法:

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;
Run Code Online (Sandbox Code Playgroud)

您可以先行一步并简化您的LogOut组件:

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

  useEffect(() => {
    logoutUser()
  });
  return <div>Logout</div>;
};
Run Code Online (Sandbox Code Playgroud)

编辑

为了简单起见,在 Logout.js 中执行以下操作:

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>;
};
Run Code Online (Sandbox Code Playgroud)

在您的登录名中,您可以使用大括号导入钩子:

import { useLogOut } from '../auth/Logout';
Run Code Online (Sandbox Code Playgroud)

在没有大括号的 index.js 中导入组件:

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


const routing = (
    <Router>
                <Route path="/logout" component={LogOut} />
    </Router>
);
Run Code Online (Sandbox Code Playgroud)