为什么我的 Auth.currentAuthenticatedUser() 方法 return 在我重新加载后更新状态,而不是在 useEffect 的依赖项运行时(或 login/logout)?

Why does my Auth.currentAuthenticatedUser() method return updated state after I reload and not when the useEffect's dependency runs (or login/logout)?

我正在 React 中创建一个 ProtectedRoute 组件,它将用户状态变量作为 prop。 这个用户来自我的 checkUser() func 使用 amplify 的 Auth.currentAuthenticatedUser().


function App() { 
  const [user, setUser] = useState();
  const { Auth, Hub } = useContext(AmplifyContext)

  async function checkUser() {
    try {
        const loggedInUser = await Auth.currentAuthenticatedUser();
        setUser(loggedInUser);
        console.log(loggedInUser);
        // get null first time?

    } catch(e) {
        setUser(null);
        console.log(e.message);
    }
}

useEffect(() => {
  checkUser();
}, [Auth])

  return (
    
        <Router>
          <Suspense fallback={<p>...loading...</p>}>
            <Switch>

              <IsUserLoggedIn user={user} loggedInPath={ROUTES.DASHBOARD} path={ROUTES.LOGIN}>
                <Route path={ROUTES.LOGIN} component={Login} />
              </IsUserLoggedIn>

              <IsUserLoggedIn user={user} loggedInPath={ROUTES.DASHBOARD} path={ROUTES.SIGN_UP}>
                <Route path={ROUTES.SIGN_UP} component={SignUp} />
              </IsUserLoggedIn>

              <ProtectedRoute user={user} path={ROUTES.DASHBOARD} exact>
                <Route path={ROUTES.DASHBOARD} exact component={Dashboard} />
              </ProtectedRoute>

              <Route path={ROUTES.RESET_PW} component={ResetPw} />
              <Route component={NoPage} />
              
            </Switch>
          </Suspense>
        </Router>
    
  );
}

// Protected Route Component
import { Route, Redirect } from "react-router-dom";
import * as ROUTES from '../constants/routes';

export default function ProtectedRoute({user, children, ...restProps}) {
    console.log(user);
    return (
        <Route
            {...restProps}
            render={({location}) => {
                if(user) {
                    return children;
                }
                if(!user) {
                    return (
                        <Redirect
                            to={{
                                pathname: ROUTES.LOGIN,
                                state: { from: location }
                                }}
            />
                    )
                }
                return null;
            }}
        />
    )
}


// login component

import { useState, useContext } from "react";
import { Link } from "react-router-dom";
import { useHistory } from 'react-router';
import AmplifyContext from "../context/amplify";
import * as ROUTES from '../constants/routes';

export default function Login() {

    const { Auth, Hub } = useContext(AmplifyContext);
    const history = useHistory();

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

    const invalid = !username || !password;

    const handleLogin = async (e) => {
        e.preventDefault();
        try {
            // amplify Auth login
            await Auth.signIn(username, password);
            history.push(ROUTES.DASHBOARD);
            console.log('logged in');
        } catch(e) {
            setError(e.message);
            setPassword('');
        }
    }

    return (
        <div className="auth-container">
            <h2 className="auth-title">Log In</h2>
            <div className="login-form-container">
                <form className="form login-form" onSubmit={handleLogin}>
                    <input autoFocus type="text" placeholder="username" aria-label="username" value={username} onChange={({target}) => setUsername(target.value)} />
                    <input type="password" placeholder="password" aria-label="password" value={password} onChange={({target}) => setPassword(target.value)} />
                    {error && (<p style={{color: 'red'}}>{error}</p>)}
                    <div className="form-action-container">
                        <div className="button-container">
                            <button disabled={invalid} className="form-button" type='submit'>Log In</button>
                            <p>Need an Account? <span><Link to={ROUTES.SIGN_UP}>Sign Up</Link></span></p>
                        </div>
                        <p>Forget your password? <span><Link to={ROUTES.RESET_PW}>Reset</Link></span></p>
                    </div>
                </form>
            </div>
        </div>
    )
}

当前的问题是 useEffect(或者可能是 Auth 方法?)没有更新状态,所以我第一次点击登录组件中的“登录”时,它 returns 'null' 来自我的 protectedRoute 组件以及主 App 组件的 console.log(user),返回 null。只有在我刷新后,它才会改变并让我获取用户日志并定向到 protectedRoute。

我的注销场景也是如此。

export default function Dashboard() {

    const { Auth, Hub } = useContext(AmplifyContext);

    const history = useHistory();

    const handleLogOut = async (e) => {
        e.preventDefault();
        // amplify call to sign out
        await Auth.signOut();

        history.push(ROUTES.LOGIN);
    }

    return (
        <div className="dashboard-container">
            <h1>Welcome </h1>
            <button onClick={handleLogOut}>log out</button>
        </div>
    )
}

我没有退出,也没有被重定向,除非我重新加载页面。 为什么 Auth.signOut() 和 Auth.currentAuthenticatedUser() 方法 运行 不像我想要的那样?

将所有与身份验证相关的状态放入上下文提供程序并将其包装在所有 {children} 组件中,然后使用 Hub 监听更改以注销后,她的工作变得更好了。 (我不得不停止使用我的路由辅助函数,所以这有点令人失望。但它目前有效。我将保留它作为解决方案)。我不得不使用自定义身份验证的放大指南,所以仍然不太满意...

需要在每个组件中使用的每个相关状态变量都从上下文中使用它(为了长度省略它)。

// storing all state related to Authorization

import { createContext, useContext, useState } from "react";
import AmplifyContext from "./amplify";

const AuthContext = createContext();

function AuthContextProvider({ children }) {
    const [formType, setFormType] = useState("signUp");
    const [fullName, setFullName] = useState("");
    const [username, setUsername] = useState("");
    const [email, setEmail] = useState("");
    const [password, setPassword] = useState("");
    const [authCode, setAuthCode] = useState("");
    const [error, setError] = useState("");

    const [user, setUser] = useState(null);
    const { Auth } = useContext(AmplifyContext);

    let invalid;

    const checkUser = async () => {
        try {
            const loggedInUser = await Auth.currentAuthenticatedUser();
            setUser(loggedInUser);
            console.log(user);
            if (user) {
                setFormType("dashboard");
            } else {
                setUser(null);
                setFormType("login");
            }
        } catch (e) {
            console.log(e.message);
        }
    };

    const handleSignUp = async (e) => {
        e.preventDefault();
        try {
            // amp auth signup. attribute must match (ie: if email is needed, state var needs to be called email (not other name))
            await Auth.signUp({ username, password, attributes: { email } });
            console.log("signed up");
            setFullName("");
            setUsername("");
            setEmail("");
            setPassword("");
            setFormType("confirmSignUp");
        } catch (e) {
            console.log(e.message);
            setError(e.message);
        }
    };

    const handleConfirmAuthCode = async (e) => {
        e.preventDefault();
        try {
            // amp auth confirm sign up
            await Auth.confirmSignUp(username, authCode);

            setFormType("login");
        } catch (e) {
            console.log(e.message);
            setError(e.message);
        }
    };

    const handleLogin = async (e) => {
        e.preventDefault();
        try {
            // amplify Auth login
            await Auth.signIn(username, password);
            setUsername("");
            setPassword("");
            console.log("logged in");
            setFormType("dashboard");
        } catch (e) {
            setError(e.message);
            setPassword("");
        }
    };

    const handleLogOut = async (e) => {
        e.preventDefault();
        // amplify call to sign out
        await Auth.signOut();
        //set some loading or redirect?
    };

    return (
        <AuthContext.Provider
            value={{
                error,
                setError,
                handleSignUp,
                checkUser,
                handleConfirmAuthCode,
                handleLogin,
                handleLogOut,
                fullName,
                setFullName,
                username,
                setUsername,
                email,
                setEmail,
                password,
                setPassword,
                formType,
                setFormType,
                authCode,
                setAuthCode,
                invalid,
                user,
                setUser,
            }}
        >
            {children}
        </AuthContext.Provider>
    );
}

export { AuthContextProvider, AuthContext };

// top
ReactDOM.render(
    <AmplifyContext.Provider value={{ Auth, Hub }}>
        <AuthContextProvider>
            <App />
        </AuthContextProvider>
    </AmplifyContext.Provider>,
    document.getElementById("root")
);

// inside the App component (not yet finished)
import { useContext, useEffect } from "react";

import AmplifyContext from "./context/amplify";
import { AuthContext } from "./context/AuthContext";
import ConfirmSignUp from "./pages/confirmSignUp";
import Login from "./pages/login";
import SignUp from "./pages/sign-up";
import Dashboard from "./pages/dashboard";
import ResetPass from "./pages/reset-pw";

function App() {
    const { Hub } = useContext(AmplifyContext);
    const {
        formType,
        setFormType,
        username,
        setUsername,
        error,
        setError,
        checkUser,
        handleLogOut,
    } = useContext(AuthContext);

    async function setAuthListener() {
        Hub.listen("auth", (data) => {
            switch (data.payload.event) {
                case "signIn":
                    console.log(`${username} signed in`);
                    break;
                case "signOut":
                    console.log("user signed out");
                    setFormType("login");
                    break;
                default:
                    break;
            }
        });
    }

    useEffect(() => {
        checkUser();
        setAuthListener();
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);

    return (
        <>
            {formType === "signUp" && <SignUp />}
            {formType === "confirmSignUp" && <ConfirmSignUp />}
            {formType === "login" && <Login />}
            {formType === "dashboard" && (
                <Dashboard handleLogOut={handleLogOut} />
            )}
            {formType === "reset" && (
                <ResetPass />
            )}
        </>
    );
}

export default App;