簡體   English   中英

React 上下文提供者在上下文消費者呈現后更新狀態

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

我正在嘗試在我的應用程序中實現受保護的路由。 我正在使用基於 cookie 的會話身份驗證。 問題是:每當我第一次嘗試訪問受保護的頁面時, RequireAuth組件的isAuthenticated值為 false ,因此它導航到/ 從控制台日志中,我可以看到Inside require auth. Inside provide 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>;
}

發生這種情況是因為useEffect中的ProvideAuth就像任何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 }} />;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM