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

React context provider updates state after context consumer renders

我正在尝试在我的应用程序中实施受保护的路由。我正在使用基于 cookie 的会话身份验证。 问题是:每当我第一次尝试访问受保护的页面时,RequireAuth 组件的 isAuthenticated 值为 false,因此它导航到 /。 从控制台日志中,我可以在 Inside provide auth..

之前看到 Inside require auth.

问题:

  1. 在上下文提供程序中使用 useEffect 是否是设置身份验证状态的正确方法?
  2. 如何确保在访问消费者中的上下文之前设置上下文提供者状态?RequireAuth

我有一个上下文提供程序 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>;
}

授权上下文消费者

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

我在 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 }} />;
}

上下文提供程序用于 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>
  );

你可以做的是检查请求是否被处理。如果处理显示加载程序,如果有任何错误显示一些错误消息或重定向。如果一切正常负载提供商。

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>;
}

发生这种情况是因为 ProvideAuth 中的 useEffect 与任何 useEffect 一样是异步任务,这意味着组件及其子组件可能会在其回调执行之前呈现。

您的问题的解决方案是在 ProvideAuth 中设置加载状态,例如调用 isCheckingAuth,默认设置为 true,并在完成所有提取后设置为 false。然后你将它传递给 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>;
}

您在 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 }} />;
}