Почему мой метод Auth.currentAuthenticatedUser() возвращает обновленное состояние после перезагрузки, а не при запуске зависимости useEffect (или входе/выходе из системы)?

#reactjs #aws-amplify #amplify

Вопрос:

Я создаю компонент ProtectedRoute в React, который будет использовать переменную состояния пользователя в качестве опоры. Этот пользователь из моей функции CheckUser (), использующей функцию Auth.currentAuthenticatedUser () от amplify.

 
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 amp;amp; (<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>
    )
}

 

Текущая проблема заключается в том, что эффект использования (или , может быть, метод аутентификации?) Не обновляет состояние, и поэтому при первом нажатии кнопки «войти» в моем компоненте входа в систему он возвращает значение «null» из моего компонента protectedRoute, а также основного компонента приложения console.log(user) , возвращая значение null. Только после того, как я обновлю, он изменится и позволит мне получить журнал пользователя, а также перенаправить его в защищенный маршрут.

Это также верно для моего сценария выхода из системы.

 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() не выполняются так, как я хочу?

Комментарии:

1. Я прочитал об усилении пользовательского потока аутентификации в руководствах ( docs.amplify.aws/руководства/аутентификация/пользовательский поток аутентификации/q/… ), но мне было интересно, можно ли сделать это по-другому? (передача данных о состоянии пользователя моим помощникам по маршруту)

Ответ №1:

это немного улучшило ее работу после того, как она поместила все состояние, связанное с аутентификацией, в поставщика контекста и обернула его вокруг всех компонентов {children}, а затем использовала концентратор для прослушивания изменений для выхода из системы. (Мне пришлось прекратить использовать мои вспомогательные функции маршрута, так что это своего рода облом. Но в данный момент это работает. Я сохраню это в качестве решения). Мне пришлось использовать руководство по усилению для пользовательской аутентификации, поэтому я все еще не очень доволен…

Каждая связанная переменная состояния, которую необходимо использовать в каждом компоненте, использует ее из контекста (опущено для длины).

 // 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" amp;amp; <SignUp />}
            {formType === "confirmSignUp" amp;amp; <ConfirmSignUp />}
            {formType === "login" amp;amp; <Login />}
            {formType === "dashboard" amp;amp; (
                <Dashboard handleLogOut={handleLogOut} />
            )}
            {formType === "reset" amp;amp; (
                <ResetPass />
            )}
        </>
    );
}

export default App;