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
.