首页 > 解决方案 > 如何等待 useAuth useEffect 返回当前用户状态?

问题描述

我在为我的 React 应用程序实现身份验证时遇到了一点问题。我按照链接进行身份验证。这是我的应用程序组件:


function App() {
  return (
    <ProvideAuth>
      <BrowserRouter>
        <Header />
        <Switch>
          <PrivateRoute exact path="/">
            <Dashboard />
          </PrivateRoute>
          <Route path="/login">
            <Login />
          </Route>
        </Switch>
      </BrowserRouter>
    </ProvideAuth>
  );
}

function PrivateRoute({ children, ...rest }) {
  let auth = useAuth();
  console.log("USER: ", auth.user);
  return (
    <Route
      {...rest}
      render={({ location }) =>
        auth.user ? (
          children
        ) : (
          <Redirect
            to={{
              pathname: "/login",
              state: { from: location }
            }}
          />
        )} />
  )
}

export default App;

登录组件:


const Login = () => {
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');

    let history = useHistory();
    let location = useLocation();
    let auth = useAuth();

    let { from } = location.state || { from: { pathname: "/" } }
    let login = (e) => {
        auth.signin(email, password, () => {
            history.replace(from);
        });
    };

    return (
        <div>
          <input onChange={e => setEmail(e.target.value)} value={email} type="email" />
          <input onChange={e => setPassword(e.target.value)} value={password} type="password" />
        </div>
    )
}

export default Login;

最后使用-auth.js:

const authContext = createContext();

export function ProvideAuth({ children }) {
    const auth = useProvideAuth();

    return <authContext.Provider value={auth}>{children}</authContext.Provider>;
};

export const useAuth = () => {
    return useContext(authContext);
};

function useProvideAuth() {
    const [user, setUser] = useState(null);

    const signin = (email, password, callback) => {
        axios.post(`${apiUrl}/sign_in`, {
            'email': email,
            'password': password
        },
            {
                headers: {
                    'Content-Type': 'application/json'
                }

            }).then(res => {
                const expiryDate = new Date(new Date().getTime() + 6 * 60 * 60 * 1000).toUTCString();
                document.cookie = `access-token=${res.headers['access-token']}; path=/; expires=${expiryDate}; secure; samesite=lax`;
                return res.data
            })
            .then(data => {
                setUser(data.data);
                callback();
            })
            .catch(e => {
                setUser(null);
            });
    };

    const signout = () => {
        document.cookie = "access-token=; expires = Thu, 01 Jan 1970 00:00:00 GMT";
        setUser(null);
    }

    useEffect(() => {
        const cookies = getCookies();
        if (cookies['access-token']) {
            axios.get(`${apiUrl}/user_info`, {
                headers: {
                    ...cookies
                }
            }).then(res => {
                return res.data;
            })
                .then(data => {
                    setUser(data);
                })
                .catch(e => {
                    setUser(null);
                })
        } else {
            setUser(null);
        }
    }, []);

    return {
        user,
        signin,
        signout
    }
}

function getCookies() {
    let cookies = document.cookie.split(';');
    let authTokens = {
        'access-token': null
    };

    for (const cookie of cookies) {
        let cookiePair = cookie.split('=');

        if (authTokens.hasOwnProperty(cookiePair[0].trim().toLowerCase()))
            authTokens[cookiePair[0].trim()] = decodeURIComponent(cookiePair[1]);
    }

    return authTokens;
}

然后仪表板组件就是主页。没什么有趣的。

问题是当用户实际上已登录时(访问令牌 cookie 以及其他令牌已设置),他们仍然被路由到登录页面,因为调用检查这些令牌是否有效的 API是异步的,因此用户最初设置为 null。

我在这里想念什么?如何在不阻塞用户界面的情况下等到 API 响应返回?我应该将用户状态保存在 redux 状态还是有其他解决方法?

非常感谢!

标签: javascriptreactjsauthenticationrouter

解决方案


就像 Jonas Wilms 建议的那样,我在 user-auth 中添加了一个类似于 user 的加载状态变量,并在每次请求之前将其设置为 true,在请求完成后将其设置为 false。

在我的 App 组件中,我更改了 PrivateRoute 函数以显示加载微调器,只要用户状态正在加载。当它设置为 false 时,我会检查用户是否登录并相应地显示仪表板组件或重定向到登录页面。

function PrivateRoute({ children, ...rest }) {
  let auth = useAuth();
  return (
    <Route
      {...rest}
      render={({ location }) =>
        auth.loading ? 
        <Loading /> :
        auth.user ? (
          children
        ) : (
          <Redirect
            to={{
              pathname: "/login",
              state: { from: location }
            }}
          />
        )} />
  )
}

推荐阅读