我对以下代码有一个问题,它快把我逼疯了.

简而言之,我想做的是:

-登录成功后,将会话存储中保存的JWT令牌状态保存.

-在某个状态下保存Boolean isTokenValid.

-通过/api/validateToken终结点的GET请求判断令牌是否有效,并相应地设置isTokenValid.

-如果令牌有效(isTokenValid=true)-->显示仪表板

-如果令牌无效(is TokenValid=False)--&>显示错误页面

  • App.jsx

    import './styles/Reset.css';
    import './styles/App.css';
    import 'bootstrap/dist/css/bootstrap.min.css';
    
    import React from 'react';
    import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
    import axios from 'axios';
    
    import Home from './components/Home';
    import Header from './components/Header';
    import Footer from './components/Footer';
    import Login from './components/Login';
    import Signup from './components/Signup';
    import Dashboard from './components/Dashboard';
    import FailedAuth from './components/FailedAuth';
    
    function App() {
    
      const [token, setToken] = React.useState(sessionStorage.getItem('token'));
      const [isTokenValid, setIsTokenValid] = React.useState(false);
      const [loading, setLoading] = React.useState(true);
    
      async function validateToken() {
        try {
          const response = await axios.get('http://localhost:5000/api/validateToken', { 
            headers: { 
              'Authorization': `Bearer ${token}` 
            } 
          });
    
          if (response.status === 200) {
            setIsTokenValid(true);
          } else {
            setIsTokenValid(false);
          }
        } catch (error) {
          setIsTokenValid(false);
        } finally {
          setLoading(false);
        }
      }
    
      React.useEffect(() => {
        async function fetchData() {
          if (token) {
            await validateToken();
          }
        }
    
        fetchData();
      });
    
      if (loading) {
        return (
          <div className="loading">
            <div className="spinner-border text-primary" role="status">
              <span className="visually-hidden">Loading...</span>
            </div>
          </div>
        );
      }
    
      return (
        <Router>
            <Routes>
              <Route path="/login/*" element={
                <>
                  <Header type="auth" />
                  <Login />
                </>
              }
              />
              <Route path="/signup/*" element={
                <>
                  <Header type="auth" />
                  <Signup />
                </>
              }
              />
              <Route path="/*" element={
                <>
                  <Header type="home" />
                  <Home />
                  <Footer />
                </>
                }
              />
              <Route path="/dashboard/*" element={
                <>
                  {isTokenValid ? <Dashboard /> : <FailedAuth />}
                </>
              }
              />
            </Routes>
        </Router>
      );
    
    }
    
    export default App;
    
  • Server.js

    app.get('/api/validateToken', passport.authenticate('jwt', { session: false }), (req, res) => {
        try {
            res.status(200).json({ message: 'Token is valid' });
        } catch (error) {
            res.status(500).json({ error: error.message });
        }
    });
    

服务器完美地处理了身份验证,并且正确地分配了JWT令牌.

我的问题恰恰在于以isTokenValid为条件的路由的呈现.

当用户登录时,它将呈现到仪表板上,即使令牌有效,它也会立即显示&lt;FailedAuth/&gt;,但如果我刷新页面,它将显示&lt;Dashboard/&gt;,正如它应该显示的那样.

令牌过期后,始终会显示&lt;FailedAuth/&gt;,所以这没有问题,它会做它应该做的事情.

我认为这是一个同步问题,但我不确定(我还是个新手,这是我的第一个项目).

有谁能帮帮我吗?这样当我发现自己面临同样的情况时,我知道将来该怎么做.

这是我设置sessionStorage时的部分代码:

import React, { useState } from 'react';
import axios from 'axios';
import { useNavigate } from 'react-router-dom';

import TipsAndUpdatesTwoToneIcon from '@mui/icons-material/TipsAndUpdatesTwoTone';
import Alert from '@mui/material/Alert';

function AuthForm(props) {
    const navigate = useNavigate();
    // Stato che contiene i dati del form
    const [formData, setFormData] = useState({
        email: '',
        username: '',
        password: '',
        confirmPassword: '',
    });

    const [error, setError] = useState('');
    const [show, setShow] = useState(true);

    function handleCloseClick() {
      setShow(false);
    }
    
    // Funzione che gestisce l'invio del form
    async function handleSubmit(event) {
        event.preventDefault();
        setTimeout(() => {
          setShow(true);
        }, 1000);
    
        try {
          let response;
    
          if (props.type === 'login') {
            response = await axios.post('http://localhost:5000/api/login', formData);

            sessionStorage.setItem('token', response.data.token);

            navigate('/dashboard');
          } else if (props.type === 'signup') {
            response = await axios.post('http://localhost:5000/api/signup', formData);
          }

          console.log(response.data);
        } catch (error) {
          console.log(error.response.data);
          if (error.response.data.error) {
            setError(error.response.data.error);
          } else {
            setError(error.response.data.message);
          }
        }

        // Resetta il form dopo l'invio
        setFormData({
          email: '',
          username: '',
          password: '',
          confirmPassword: '',
        });
    };
    
    // Funzione che gestisce i cambiamenti dei campi del form
    function handleChange(event) {
        setFormData({ ...formData, [event.target.name]: event.target.value });
    };
    
    // Renderizza il form
    return (
        <>
          {error && show && (
            <Alert onClose={() => {handleCloseClick()}} severity="error" className='mb-4'>{error}</Alert>
          )}
          <div className="d-flex justify-content-center">
            <h1 className='icon-yellow'>
              {props.type === 'login' ? 'Log In' : 'Sign Up'}{' '}
              <TipsAndUpdatesTwoToneIcon className='icon-yellow' style={{ fontSize: '3.5rem' }}/>
            </h1>
          </div>
          <form className="auth-form" onSubmit={handleSubmit}>
            <div className="form-group">
              <label htmlFor="email">Email</label>
              <input className="form-control" type="email" name="email" value={formData.email} onChange={handleChange} />
            </div>
            {props.type === 'signup' && (
              <div className="form-group">
                <label htmlFor="username">Username</label>
                <input className="form-control" type="text" name="username" value={formData.username} onChange={handleChange} />
              </div>
            )}
            <div className="form-group">
              <label htmlFor="password">Password</label>
              <input className="form-control" type="password" name="password" value={formData.password} onChange={handleChange} />
            </div>
            {props.type === 'signup' && (
              <div className="form-group">
                <label htmlFor="confirmPassword">Confirm Password</label>
                <input className="form-control" type="password" name="confirmPassword" value={formData.confirmPassword} onChange={handleChange} />
              </div>
            )}
            <button className="auth-button" type="submit">
              {props.type === 'login' ? 'Login' : 'Sign Up'}
            </button>
          </form>
        </>
    );
}

export default AuthForm;

推荐答案

Issue

在会话存储中设置令牌后,您尚未使用setToken设置令牌状态. 在挂载App组件时,sessionStorage.getItem('token')未定义.您将其误认为在您设置会话存储之后它将从会话存储中获取令牌,这不是Reaction状态的工作方式.在初始安装时,只将状态设置为初始值,之后必须使用setter函数对其进行变异.

const [token,setToken] = useState(initialValue)

在依赖数组原因、setIsTokenValid(TRUE)和setLoding(FALSE)的状态更改后,没有将其设置为相同的内容,则您是安全的. 这就是Reaction无限停止重新渲染的原因.如果注意到任何状态更改,则会发生Reaction重新呈现.

没有依赖数组->;

  1. setCount((prev)=>!prev)->;导致无限重新渲染
  2. setCount(true/false)->;没有无限重渲染.

Solution

从令牌的初始状态中删除essionStorage.getItem(‘Token’).

const [token, setToken] = React.useState(null);

添加[Token]以使用Effect依赖项数组

当令牌状态更改时,它将触发重新呈现,从而触发useEffect、Token将验证,并将isValidToken进一步设置为True.

useEffect(() => {
  async function fetchData() {
    if (token) {
      await validateToken();
    }
  }

  fetchData();
}, [token]);

Reactjs相关问答推荐

将cy.get()与动态类名一起使用?

使用useReducer时,props 未从父级传递给子或孙级

如何将图像(在代码中称为自己)定位在蓝色圈内的点上?我正在使用material UI和Reaction

关于同一位置的不同组件实例的react S规则被视为相同组件

为什么我的React App.js只在页面刷新时呈现3次?

为多租户SSO登录配置Azure AD应用程序

获取未捕获的错误:Gatsby生产版本上的最小化react 错误#418

Reaction路由-如何在布局路由内呈现&q;/:id&q;路由

使用VITE的Reaction应用程序的配置是否正确?

单击按钮不会切换组件(当按钮和组件位于两个单独的文件中时)

Reactjs:MUI5:MobileDateTimePicker API:如何自定义日历中的文本

使用D3.js绘制和展示弦图的右下方象限

redux 工具包使用中间件构建器函数时,必须返回一个中间件数组

Material UI v5.11 - Prop sx 在响应式中写了两次

为什么 setState 在 React 中不是异步的?

DatePicker MUI 如何在日历左侧显示清除文本

添加新行时自动打开可扩展表格(Antd 表格,ReactJS)

有没有办法根据 Rechart 中的 activeDot 更改值?

如何从 React Redux store 中删除特定项目?

如何使图标适合 react-bootstrap 中的行元素