简体   繁体   English

React 上下文提供者在上下文消费者呈现后更新状态

[英]React context provider updates state after context consumer renders

I am trying to implement Protected Routes in my app.我正在尝试在我的应用程序中实现受保护的路由。 I am using cookie-based session authentication.我正在使用基于 cookie 的会话身份验证。 The issue is: Whenever I try to access a protected page for the first time, the RequireAuth component has the isAuthenticated value as false and hence it navigates to / .问题是:每当我第一次尝试访问受保护的页面时, RequireAuth组件的isAuthenticated值为 false ,因此它导航到/ From the console logs, I can see Inside require auth.从控制台日志中,我可以看到Inside require auth. before Inside provide auth.Inside provide auth. . .

Questions:问题:

  1. Is using useEffect in the context provider the right way to set the auth state?在上下文提供程序中使用useEffect是设置身份验证状态的正确方法吗?
  2. How do I make sure that the context provider state is set before accessing the context in the consumer RequireAuth ?如何确保在访问消费者RequireAuth中的上下文之前设置上下文提供者状态?

I have a context provider ProvideAuth which makes an API call to check if the user is already authenticated.我有一个上下文提供者ProvideAuth ,它会调用 API 来检查用户是否已经通过身份验证。


const authContext = createContext();

export function ProvideAuth({ children }) {
    const navigate = useNavigate();
    const location = useLocation();
    const [isAuthenticated, setIsAuthenticated] = useState(false);
    const [userInfo, setUserInfo] = useState({});
    
    const fetchData = async () => {
        const isAuthenticated = await CheckAuthentication();
        setIsAuthenticated(isAuthenticated);
        if (isAuthenticated) {
            const userInfo = await GetUserInfo();
            setUserInfo(userInfo);
        }
    }

    useEffect(() => {
        console.log("Inside provide auth. " + isAuthenticated + " " + location.pathname);
        fetchData();
    }, []);

    const value = {
        isAuthenticated,
        userInfo
    };

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

Auth context consumer验证上下文消费者

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

I use the context in a RequireAuth component to check if the user is already authenticated and redirect if not.我使用RequireAuth组件中的上下文来检查用户是否已经通过身份验证,如果没有则重定向。

export default function RequireAuth({ children }) {
    const { isAuthenticated, userInfo } = useAuth();
    const location = useLocation();

    useEffect(() => {
        console.log("Inside require auth. " + isAuthenticated + " " + location.pathname);
    }, []);

    return isAuthenticated === true ?
        (children ? children : <Outlet />) : 
        <Navigate to="/" replace state={{ from: location }} />;
}

The context provider is used in the App.js上下文提供程序在 App.js 中使用

return (
    <ProvideAuth>
      <div className='App'>
        <Routes>
          <Route exact path="/" element={<Home />} />
          <Route path="/pricing" element={<Pricing />} />
          <Route element={<RequireAuth /> }>
            <Route path="/jobs" element={<Jobs />} >
              <Route index element={<MyJobs />} />
              <Route path="new" element={<NewJob />} />
              <Route path=":jobId" element={<JobDetails />} />
              <Route path=":jobId/stats" element={<JobStats />} />
            </Route>
          </Route>
          <Route path="*" element={<NotFound />} />
        </Routes>
      </div>
    </ProvideAuth>
  );

What you can do is check, If the request is processed or not.您可以做的是检查请求是否已处理。 If processing show loader if any error shows some error msg or redirect.如果处理显示加载程序,如果任何错误显示一些错误消息或重定向。 If everything is fine load provider.如果一切正常,负载提供商。

const authContext = createContext();

export function ProvideAuth({ children }) {
  const [state, setState] = useState({
    user: null,
    isAuthenticated: false,
    isLoading: false,
    error: null,
  });

  useEffect(() => {
    const fetchData = async () => {
      try {
        const isAuthenticated = await CheckAuthentication();
        if (isAuthenticated) {
          const user = await GetUserInfo();
          setState((prev) => ({ ...prev, isAuthenticated, user }));
        }
      } catch (error) {
        setState((prev) => ({ ...prev, error }));
      } finally {
        setState((prev) => ({ ...prev, isLoading: false }));
      }
    };
    fetchData();
  }, []);

  if (state.isLoading) return <Loading />;
  if (state.error) return <ErrorMessage error={state.error} />;
  return <authContext.Provider value={state}>{children}</authContext.Provider>;
}

That's happening because that useEffect in ProvideAuth is as any useEffect is an asynchronous task, which means the component and its children may render before its callback gets executed.发生这种情况是因为useEffect中的ProvideAuth就像任何useEffect都是异步任务一样,这意味着组件及其子组件可能会在其回调执行之前呈现。

A solution to your problem is to set up a loading state in ProvideAuth , called for example isCheckingAuth , set to true by default, and to false after you have done all the fetching.解决您的问题的方法是在ProvideAuth中设置加载状态,例如称为isCheckingAuth ,默认设置为 true,并在完成所有获取后设置为 false。 And you pass it down to RequireAuth , like so :然后将其传递给RequireAuth ,如下所示:

const authContext = createContext();

export function ProvideAuth({ children }) {
    const navigate = useNavigate();
    const location = useLocation();
    const [isCheckingAuth, setIsCheckingAuth] = useState(true);
    const [isAuthenticated, setIsAuthenticated] = useState(false);
    const [userInfo, setUserInfo] = useState({});
    
    const fetchData = async () => {
        const isAuthenticated = await CheckAuthentication();
        setIsAuthenticated(isAuthenticated);
        if (isAuthenticated) {
            const userInfo = await GetUserInfo();
            setUserInfo(userInfo);
        }
        setIsCheckingAuth(false)
    }

    useEffect(() => {
        console.log("Inside provide auth. " + isAuthenticated + " " + location.pathname);
        fetchData();
    }, []);

    const value = {
        isAuthenticated,
        userInfo,
        isCheckingAuth
    };

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

You use that isCheckingAuth in RequireAuth to show a loader while the fetching is being done, this way:您在RequireAuth isCheckingAuth在获取完成时显示加载程序,这样:

export default function RequireAuth({ children }) {
    const { isAuthenticated, userInfo, isCheckingAuth } = useAuth();
    const location = useLocation();
    
     useEffect(() => {
       if(isCheckingAuth) return;
       console.log("Inside require auth. " + isAuthenticated + " " + location.pathname);
     }, [isCheckingAuth]);
    
    if(isCheckingAuth) return <div>Loading...</div>

    return isAuthenticated === true ?
        (children ? children : <Outlet />) : 
        <Navigate to="/" replace state={{ from: location }} />;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM