上下文值没有及时更新

问题描述 投票:0回答:1

上下文.tsx

import { PropsWithChildren, createContext, useContext, useState } from "react";
import auth_svc from "../services/authServices";
import { Result, message } from "antd";

interface Result{
  _id: String | null;
  fullname: String | null;
  accessToken: String | null;
}
type usertypes = Result | null;


type AuthContextType = {
  verifyToken: () => Promise<void>;
  user: usertypes;
  logoutUser: () => void;
};

type AuthProviderProps = PropsWithChildren<{}>;

const defaultAuthContext: AuthContextType = {
  verifyToken: async () => {},
  user: null,
  logoutUser: () => null,
};

const AuthContext = createContext<AuthContextType>(defaultAuthContext);

export const useAuth = () => {
  const context = useContext(AuthContext);
  if (context === undefined) {
    throw new Error("useAuth must be used within AuthProvider");
  }
  return context;
};

export default function AuthProvider({ children }: AuthProviderProps) {
  const [user, setuser] = useState<usertypes | null>(null);
  async function verifyToken(): Promise<void> {
    try {
      const token = localStorage.getItem("accessToken");
      if (token && !user) {
        let res = await auth_svc.getLoggedInUser();
        setuser(res);
      }
    } catch (err) {
      throw err;
    }
  }

  const logoutUser = (): void => {
    try {
      localStorage.clear();
      setuser(null);
    } catch (err) {
      throw err;
    }
  };

  return (
    <AuthContext.Provider value={{ verifyToken, user, logoutUser }}>
      {children}
    </AuthContext.Provider>
  );
}

私人路线

import { ReactNode, useEffect, useMemo, useState } from "react";
import { useNavigate } from "react-router-dom";
import { useAuth } from "./context/context";
import auth_svc from "./services/authServices";
import { Spin } from "antd";

const PrivateRoutes = ({ children }: { children: ReactNode }) => {
  const { user,verifyToken} = useAuth();
  const [loader, setLoader] = useState<boolean>(true);
  const navigate = useNavigate();
  useEffect(() => {
    const validateToken = async () => {
      await verifyToken();
      if(!user) {
        navigate("/signin",{replace:true})
      } else {
        setLoader(false)
      }
    }
   validateToken();
  }, [user]);
  return loader ?  <Spin fullscreen={true} /> : children;
};

export default PrivateRoutes;

登录功能

const Login = () => {
  const navigate = useNavigate();
  const [loading, setLoading] = useState<boolean>(false);
  const onFinish: FormProps<FieldType>["onFinish"] = async (values) => {
    setLoading(true);
    let response = await auth_svc.login(values);
    if (response) {
      setLoading(false);
      navigate("/", { replace: true });
    }
  };

路由功能

const router = createBrowserRouter([
  {
    path: "/signin",
    element: <Login key="adminlogin" />,
  },
  {
    path: "/",
    element: (
      <PrivateRoutes>
        <App key="app" />
      </PrivateRoutes>
    ),
  },
 
]);

const Routing = () => {
  return (
    <AuthProvider>
      <Provider store={store}>
        <RouterProvider router={router} />
      </Provider>
    </AuthProvider>
  );
};

身份验证服务

import HttpServices from "./httpService";

class AuthServices extends HttpServices {
  login = async (data: {}) => {
    try {
      let res = await this.postRequest("signin", data, {});
      if (res) {
        localStorage.setItem("accessToken", res.data.result.accessToken);
        localStorage.setItem("user", res.data.result.fullname);
        return true;
      }
    } catch (error) {
      throw error;
    }
  };

  getLoggedInUser = async () => {
    try {
      const response = await this.getRequest("/signin", {
        login: true,
      });
      if (response) return response.data.result;
    } catch (error) {
      throw error;
    }
  };
}

const auth_svc = new AuthServices();
export default auth_svc;

所以我在这里尝试的是,当我单击登录 auth_Svc.login 函数时,检索值并将令牌和名称保存在本地存储中,因此在登录函数中响应为 true 后,它会重定向到受 privateRoute 保护的“/”主页私有路由它检查上下文中是否有用户。上下文由函数 verifytoken 更新,该函数从本地存储获取令牌并验证成功后将其存储到上下文中,但这里的问题是它不会在第一次单击时通过 privateRoute 渲染子组件登录按钮,但第二次登录了,是什么原因导致我检查了所有内容,上下文值第一次不是在 privateRoute 中检索,而是第二次??

reactjs react-router react-context react-typescript
1个回答
0
投票

问题

问题是日志记录不会更新

user
中的
AuthContext
状态。当然,localStorage 已更新,但仅此还不足以触发 React 执行任何操作。直到您重新加载页面并在
verifyToken
挂钩中调用
useEffect
函数来读取 localStorage 并更新
user
状态。

解决方案建议

我建议在

login
中创建一个
AuthContext
处理程序,调用
auth_svc.login
函数并在
user
组件处理程序之前更新
Login
状态,然后导航到
"/"
路径。

示例:

AuthProvider

创建一个

login
处理程序,调用并等待
auth_svc.login
方法并传递身份验证有效负载,并在成功身份验证并获取当前用户后更新
user
状态。

export default function AuthProvider({ children }: AuthProviderProps) {
  const [user, setUser] = useState<usertypes | null>(null);

  async function verifyToken(): Promise<void> {
    try {
      const token = localStorage.getItem("accessToken");
      if (token && !user) {
        let res = await auth_svc.getLoggedInUser();
        setUser(res);
      }
    } catch (err) {
      throw err;
    }
  }

  const loginUser = async (data: {}) => {
    try {
      const user = await auth_svc.login(data);
      setUser(user);
    } catch (err) {
      // catch and handle/ignore
      throw err; // rethrow for callees
    }
  };

  const logoutUser = (): void => {
    try {
      localStorage.clear();
      setuser(null);
    } catch (err) {
      throw err;
    }
  };

  return (
    <AuthContext.Provider value={{ verifyToken, user, loginUser, logoutUser }}>
      {children}
    </AuthContext.Provider>
  );
}

身份验证服务

更新

login
处理程序以在成功登录时调用
getLoggedInUser
方法,以获取当前用户对象并将其返回给被调用者。

class AuthServices extends HttpServices {
  login = async (data: {}) => {
    try {
      const res = await this.postRequest("signin", data, {});
      if (res) {
        localStorage.setItem("accessToken", res.data.result.accessToken);
        localStorage.setItem("user", res.data.result.fullname);
      }
      return this.getLoggedInUser(); // <-- return "user"
    } catch (error) {
      throw error;
    }
  };

  getLoggedInUser = async () => {
    try {
      const response = await this.getRequest("/signin", {
        login: true,
      });
      return response.data.result;
    } catch (error) {
      throw error;
    }
  };
}

登录

登录只需调用并等待从

loginUser
访问的
AuthContext
回调即可。如果没有抛出错误或者没有 Promise 拒绝,那么当前用户的登录和获取应该已经成功,并且处理程序可以重定向到
"/"

const Login = () => {
  const navigate = useNavigate();
  const { loginUser } = useAuth();
  const [loading, setLoading] = useState<boolean>(false);

  const onFinish: FormProps<FieldType>["onFinish"] = async (values) => {
    setLoading(true);
    try {
      await loginUser(values);
      // No thrown error so login succeeded
      navigate("/", { replace: true });
    } catch (err) {
      // catch and handle/ignore
    } finally {
      setLoading(false);
    }
  };

  ...
};
© www.soinside.com 2019 - 2024. All rights reserved.