【问题标题】:context is slow or being called twice上下文很慢或被调用两次
【发布时间】:2020-06-01 19:00:13
【问题描述】:

我用于身份验证的上下文存在问题。如果我在登录时 console.log,首先我得到错误,然后一秒钟后它再次使用令牌等记录。所以,我的代码有问题。

这是我的身份验证钩子:

import { useState, useCallback, useEffect } from "react";

export const useAuth = () => {
  const [token, setToken] = useState(false);
  const [userId, setUserId] = useState(false);

  const login = useCallback((uid, token) => {
    setToken(token);
    setUserId(uid);
    localStorage.setItem(
      "userData",
      JSON.stringify({
        userId: uid,
        token: token,
      })
    );
  }, []);

  const logout = useCallback(() => {
    setToken(null);
    setUserId(null);
    localStorage.removeItem("userData");
  }, []);

  useEffect(() => {
    const storedData = JSON.parse(localStorage.getItem("userData"));
    if (storedData && storedData.token) {
      login(storedData.userId, storedData.token);
    }
  }, [login]);

  return { token, login, logout, userId };
};

身份验证上下文:

import { createContext } from "react";

export const AuthContext = createContext({
  isLoggedIn: false,
  userId: null,
  token: null,
  login: () => {},
  logout: () => {},
});

我遇到问题的组件示例(基本上在我使用此身份验证上下文的所有组件中)

import React, { useContext, useState } from "react";
import AdminCard from "../components/adminCards/AdminCard";
import { AuthContext } from "../../shared/context/auth-context";

const Dashboard = () => {
  const auth = useContext(AuthContext);
  console.log(auth);
  return (
    <div className="admin-wrapper">
      <div className="main-panel">
        <div className="row">
          <div className="wrapper">
            <div className="col-3">
              <AdminCard title="Contact Enquiries" />
              <AdminCard title="Card 2" />
              <AdminCard title="Card 3" />
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

export default Dashboard;

在控制台中,我首先看到“false”,然后一秒钟后,我看到了“auth”的内容。

App.js 示例

const App = () => {
   const { token, login, logout, userId } = useAuth();
  return (
    <>
      <AuthContext.Provider
        value={{
          isLoggedIn: !!token,
          token: token,
          userId: userId,
          login: login,
          logout: logout,
        }}
      >
 <Router>
<Switch>
   <Route path="/" exact>
    <Dashboard />
   </Route>
</Switch>
</Router>
</AuthContext.Provider>
</>
};

export default App;

问题的一个例子是,如果我通过单击链接登录并导航到仪表板,我没有问题。如果我在仪表板上并刷新浏览器,则会收到 403 错误并且请求失败,因为发送请求时令牌尚不可用。

  useEffect(() => {
    const getItems = async () => {
      const config = {
        method: "get",
        url: "http://localhost:8000/api/admin/somedata",
        headers: { Authorization: "Bearer " + auth.token },
      };
      const responseData = await Axios(config);
      setItems(responseData.data);
      console.log(responseData.data);
    };
    getItems();
  }, []);

为了澄清,我只有在刷新浏览器时才会遇到这个问题。

【问题讨论】:

  • 我没有看到你在哪里使用 authuseContext(AuthContext) 或者你在组件树中的哪里有 AuthContext.Provider。如果你没有这两件事的代码,那肯定会导致你的问题;如果您有代码,请将其发布为尽可能简化的表格。
  • React 重新渲染更改。您将 loggedIn 设置为 false 以进行初始渲染。登录将重新呈现所需的上下文和子组件。这就是 react 的工作原理,并不意味着它很慢。
  • @JoshWilson,添加了我的 App.js 的快速示例供您查看
  • @Andre,问题是当我尝试在受保护的管理区域中发出获取请求时,它最初会失败,因为我必须在标头中发送令牌,但因为令牌在第一次加载时不存在, 它失败。这是我的问题,第一个 console.log 为假,然后一秒钟后我在控制台中看到了令牌。我想在第一个 console.log 上看到它,不希望它加载两次

标签: reactjs react-hooks react-context


【解决方案1】:

您可以稍微简化您的 useAuth 钩子,以便它立即返回结果,而不仅仅是重新渲染。

export const useAuth = () => {
  const [{
    // normalize undefined -> null
    userId = null,
    token = null
  // init state from localStorage
  }, setUserData] = useState(() => JSON.parse(localStorage.getItem("userData") || "{}"));

  // only update the context if userId or token changed
  return useMemo(() => {
    if (userId || token) {
      localStorage.setItem("userData", JSON.stringify({ userId, token }));
    } else {
      localStorage.removeItem("userData");
    }

    // There's no point of putting these functions into useCallback, 
    // unless you use them somewhere as deps.

    // TODO: validate login? disable login while logged in?
    const login = (userId, token) => setUserData({ userId, token });
    const logout = () => setUserData({});

    return {
      userId,
      token,
      isLoggedIn: !!token,
      login,
      logout
    };
  }, [userId, token]);
};

那么App就变成了

const App = () => {
  const authContext = useAuth();
  return (
    <AuthContext.Provider value={authContext}>
      <Router>
        <Switch>
          <Route path="/" exact>
            <Dashboard />
          </Route>
        </Switch>
      </Router>
    </AuthContext.Provider>
  );
};

那么,你的useEffect显然依赖于token;至少它的存在。你需要把它放在deps

至少是这样的:

useEffect(() => {
  const getItems = async () => {
    const config = {
      method: "get",
      url: "http://localhost:8000/api/admin/somedata",
      headers: { Authorization: "Bearer " + auth.token },
    };
    const responseData = await Axios(config);
    setItems(responseData.data);
    console.log(responseData.data);
  };
  auth.token && getItems();
}, [!!auth.token]);

【讨论】:

    【解决方案2】:

    这就是 React 本身。你可以做的是,你可以使用如下的 useEffect 钩子,

    useEffect(() => {
      auth && doSomethingWithAuth(auth);
    }, [auth])
    

    但是,理想的解决方案是在读取您的身份验证令牌时显示一些加载组件。您基本上可以将您的提供者提取为另一个组件。然后您可以尝试在您的提供程序组件中读取您的 auth 变量。在开始读取操作时,将状态设置为 authLoading 并在 auth 变为真时将其设置为 false(再次使用 useEffect 挂钩)。最后,如果 authLoading === true,则显示一些加载组件/文本,否则显示提供者的孩子。

    希望这会有所帮助!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-03-20
      • 2015-12-18
      • 2017-05-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-01
      相关资源
      最近更新 更多