I am attempting to use React's useEffect
hook to run a fetch command and store the result in state. I would then like to use that state value to conditionally render a React Route component. I am having trouble setting the value of that variable in state before the component runs the conditional statement and returns the Route component.
Express code:
app.post('/refresh_token_from_db', async (req: Request, res: Response) => {
res.send(true)
})
ProtectedRoutes:
import { Navigate, Outlet } from 'react-router-dom'
import { useState, useEffect } from 'react';
function ProtectedRoutes() {
const [token, setToken] = useState<boolean>(false);
// Data does't start loading
// until *after* component is mounted
useEffect(() => {
const go = async () => {
await fetch('/refresh_token_from_db', {
method: 'POST',
mode: 'same-origin',
redirect: 'follow',
credentials: 'same-origin',
headers: {
'content-type': 'application/json',
'Accept': 'application/json'
},
}).then(res => res.json())
.then(data => {
console.log("Should run 1st", data);
setToken(data)
})
}
go()
}, []);
console.log("Should run 2nd", token);
return (
token ? <Outlet /> : <Navigate to="/login" />
);
}
export default ProtectedRoutes
CodePudding user response:
If I understand you correctly, you want your component to wait for the fetch response, and then decide whether to redirect you to login or not.
In that case, add another state called isWaiting and set it initially to be true, then call setIsWaiting(false) just under setToken(data);
Finally, change the return to this:
return (
token && !isWaiting ? <Outlet /> : <Navigate to="/login" />
);
CodePudding user response:
Fetching the data as well as executing the callback of an useEffect
and mutating the state
in React are asynchronous tasks. Which means this code:
return (
token ? <Outlet /> : <Navigate to="/login" />
);
might run before, and which is happening to cause that earlier redirection. Sadly you cannot or very hardly dictate the order of execution here. The workaround is to use an additional sate, I called it checking
, like so:
import { useEffect, useState } from 'react';
import { Navigate, Outlet } from 'react-router-dom';
function ProtectedRoutes() {
const [token, setToken] = useState<boolean>(false);
const [checking, setIsChecking] = useState<boolean>(true);
// Data does't start loading
// until *after* component is mounted
useEffect(() => {
const go = async () => {
await fetch('/refresh_token_from_db', {
method: 'POST',
mode: 'same-origin',
redirect: 'follow',
credentials: 'same-origin',
headers: {
'content-type': 'application/json',
'Accept': 'application/json'
},
}).then(res => res.json())
.then(data => {
console.log("Should run 1st", data);
setToken(data)
setIsChecking(false)
})
}
go()
}, []);
console.log("Should run 2nd", token);
if(checking) return <p>Loading...</p>
return (
token ? <Outlet /> : <Navigate to="/login" />
);
}
export default ProtectedRoutes