React:必须单击两次表单提交才能使用 useContext 全局设置上下文

React: Form submit must be clicked twice to set context globally using useContext

这里是 React 初学者。我正在使用 jwt、axios 和 useContext 在 React 中构建登录表单。从后端获得授权后,我使用 AuthProvider 将数据存储在全局上下文中并重定向到主页。主页首先检查授权并导航到未经授权的访问登录。但是,即使在登录时更新了 auth (useState),我在第一次点击时仍然会得到一个错误的值,即使经过授权也会被发送回登录。我到处都试过 useEffects 但无济于事。下面的代码

AuthProvider.jsx

import React, { useState } from "react";
import { createContext } from "react";

const AuthContext = createContext();
export default AuthContext

export const AuthProvider = ({ children }) => {
    const [user, setUser] = useState(null);
    const [authed, setAuthed] = useState(false);

    function login(user) {
        setUser(user);
        setAuthed(true);
    }
    
    return (
        <AuthContext.Provider value={{login, user, authed}}>
            {children}
        </AuthContext.Provider>
    )
}

ProtectedRoute.jsx 到 /home

import React, { useContext, useEffect } from "react";
import AuthContext from "../../context/AuthProvide";

const ProtectedRoute = ({children}) => {

    const {login, user, authed} = useContext(AuthContext);
    useEffect(() => {
        alert("HELLO")
        alert(authed)
        if (!authed) {
            return window.location.href = "/login";
        } else {
            return children
        }
    }, [authed, user, login]);
}
export default ProtectedRoute;

Login.jsx

的顶部
import React, { useState, useEffect, useRef, useContext } from "react";
import "./login.css";
import AuthContext from "../../context/AuthProvide";

import { axios } from "../../context/axios";
const LOGIN = "/login";

const Login = () => {
    const {login, user, authed} = useContext(AuthContext);
    const [userEmail, setUserEmail] = useState("");

    const [userPassword, setUserPassword] = useState("");
    const [error, setError] = useState("");

    const errorRef = useRef();
    useEffect(() => {
        
    }, [authed, user, login]);

    useEffect(() => {
        setError("");
    }, [userEmail, userPassword]);


    function handleUserEmail(event) {
        setUserEmail(event.target.value);
    }

    function handleUserPassword(event) {
        setUserPassword(event.target.value);
    }

    function handleSubmit(event) {
        event.preventDefault();
        axios.post(LOGIN, {
            email: userEmail,
            password: userPassword
        }).then(response => {
            if (response.data.error) {
                setError(response.data.error);
            } else {
                // this is supposed to be the one to set the user and auth to true
                login(response.data.token)
                alert(authed)
                    window.location.href = "/";
            }
        }).catch(error => {
            if (!error?.response) {
                setError("NO SERVER RESPONSE");
            } else if (error.response?.status === 400) {
                setError("MISSING USER NAME OR PASSWORD");
            } else if (error.response?.status === 401) {
                setError("UNAUTHORIZED ACCESS");
            } else {
                setError("UNKNOWN ERROR");
            }
            errorRef.current.focus();
        })
    }

    function resetForm() {
        setUserEmail("");
        setUserPassword("");
    }
  return (
  // the form is here
  )

问题

我发现代码的主要问题是 window.location.href 的使用。使用它时,它会重新加载页面。这将重新安装整个应用程序,任何 React 状态都将是 lost/reset,除非它被持久化到 localStorage 并用于初始化应用程序状态。

更常见的是使用react-router-dom中的导航工具(我假设这是正在使用的包,但原理翻译)来发布命令式和声明式导航操作。

建议

受保护的路由组件应该重定向到登录路径,或者在用户获得授权的情况下呈现 children 道具。它在路由状态中传递正在访问的当前位置,以便用户可以在成功验证后重定向回来。

import { Navigate } from 'react-router-dom';

const ProtectedRoute = ({ children }) => {
  const location = useLocation();
  const { authed } = useContext(AuthContext);
  if (!authed) {
    return <Navigate to="/login" replace state={{ from: location }} />;
  } else {
    return children;
  }
};

Login 组件应该使用 useNavigate 挂钩来使用 navigate 函数在验证后将用户重定向到受保护的路由。

import { useLocation, useNavigate } from 'react-router-dom';

const Login = () => {
  const { state } = useLocation();
  const navigate = useNavigate();
  const { login, user, authed } = useContext(AuthContext);
  ...

  function handleSubmit(event) {
    event.preventDefault();
    axios.post(
      LOGIN,
      {
        email: userEmail,
        password: userPassword
      }
    )
      .then(response => {
        if (response.data.error) {
          setError(response.data.error);
        } else {
          login(response.data.token)
          navigate(state?.from?.pathname ?? "/", { replace: true });
        }
      })
      .catch(error => {
        if (!error?.response) {
          setError("NO SERVER RESPONSE");
        } else if (error.response?.status === 400) {
          setError("MISSING USER NAME OR PASSWORD");
        } else if (error.response?.status === 401) {
          setError("UNAUTHORIZED ACCESS");
        } else {
          setError("UNKNOWN ERROR");
        }
        errorRef.current.focus();
      });
  }

  ...

  return (
    // the form is here
  );
}

ProtectedRoute 组件包装您要保护的路由。

<AuthProvider>
  <Routes>
    ...
    <Route path="/login" element={<Login />} />
    <Route
      path="/test"
      element={
        <ProtectedRoute>
          <h1>Protected Test Route</h1>
        </ProtectedRoute>
      }
    />
  </Routes>
</AuthProvider>