javascriptreactjsauthenticationhydration

State rendering issue, is this the right pattern?


So I have this kind of issue, having a React Context where I manage authentication I use a protected routes component to disable accessing pages when the user is not authenticated (no localstorage user data available).

But there is a problem with this approach, because at the first pre-render the user is undefined (useState()) and only after useEffect runs the user data is fetched.

Snippet code:

AuthContextProvider

  const [user, setUser] = useState();
  const [hydrated, setHydrated] = useState(false); // added to fix the problem

  useEffect(() => {
    const userLocal = localStorage.getItem('user');
    setUser(JSON.parse(userLocal));
    setHydrated(true); // added to fix the problem
  }, []);

Protected routes:

  if (isBrowser() && !authCtx.user && pathIsProtected) {
    router.push('/signin');
  }

So I end up having user = undefined for a fraction of second that is enough to catch the condition !authCtx.user, instead the user is authenticated.

As you can see in the code my workaround that works is to use a hydrated variable which I set together with the user data from localstorage and in the provider I wait the hydrated variable to be true before rendering the children components (where the protected route is)

  return (
    <AuthContext.Provider
      value={{
        error: error,
        loading: loading,
        user: user
      }}
    >
      {hydrated && children} {/* added to fix the problem */}
    </AuthContext.Provider>
  );

This way everything works fine but is this the right pattern for this kind of problem?


Solution

  • You can do this, which is almost the same but uses user variable instead of hydrated:

    const [user, setUser] = useState(false);
    

    And in the component:

      ...
      >
        {user && children} {/* added to fix the problem */}
      </AuthContext.Provider>
      ...
    

    This way you can drop const [hydrated, setHydrated] = useState(false); altogether.