Home > Software engineering >  React-Router Race Condition
React-Router Race Condition

Time:01-30

I'm trying to implement protected routes. The issue is, the navigate happens before the setSession has updated meaning the authContext is still false and the protected route component sends the user back to /sign-in

This is the handleSubmit function on my sign in form

    const handleSubmit =
      async (e) => {
        e.preventDefault();

        let { data, error } = 
          await auth.signIn({
            email,
            password
          })

        if (error)
          setAuthError(error.message);

        if (data)
          navigate('/dashboard');
      }

This is the signIn function on my context, called by the function above

async ({ email, password }) => {
        let { data, error } =
          await client.auth.signInWithPassword({
            email,
            password
          })

        if (data)
          setSession(data.user)

        return { data, error }
      },

...and of course the protected route component is essentially

let { isSignedIn } = useContext(AuthContext);
    
    return (
      isSignedIn
        ? children
        : <Navigate to="/sign-in" replace />
    )

From looking around this seems to be the basic structure that protected route tutorials have: use a handler function to call the sign-in function on a context; context sets some state and returns; the handler function then navigates.

I'm using React-Router 6.8.0. Funnily enough, the sign-in/out button in the nav (which is not under the react router <Outlet/> seems to work)

CodePudding user response:

I think you need to have a separate state for managing when the checking of the auth status is happening. At the the minute your logic doesn't cover that scenario, for example, something like -

let { isSignedIn, isAuthLoading } = useContext(AuthContext);

   if(isAuthLoading) return <Loading />
    
    return (
      isSignedIn
        ? children
        : <Navigate to="/sign-in" replace />
    )

or whatever way you feel best suits your app structure, but essentially, you need to have some way of handling the states that represent when you're either checking if the user is authenticated, or during authentication, then you can use your existing private route logic when you know if the user is logged in or not.

CodePudding user response:

From what I can tell this is due to "batching" - https://github.com/reactwg/react-18/discussions/21, the fix was to use flushSync around the setSession state call.

React applies the setSession call asynchronously, and in this case the state still hasn't been updated by the time the <ProtectedRoute/> component is hit (after we've navigated with navigate('/dashboard')), so isSignedIn is still false so we get sent back to /sign-in.

I'm guessing the majority of those "protected routes" tutorials are using React < 18, where there was no batching of setState calls outside of event handlers i.e inside promises which means they updated synchronously and thus the above pattern worked without the use of flushSync.

  • Related