Reputation: 21
Have some confusion in using the react state, as from my code below, when trying to hit the API, the res2
will be running twice, and res3
will be running 4 times, and so on going deeper on the nested if.
Is there any way to only hit each one of the API chains once, then after hitting each API would update the taskNum
state and render it on screen?
function Loading() {
const [taskNum, setTaskNum] = useState(1);
const location = useLocation();
const client = axios.create({
baseURL: "https://localhost:8000"
});
const predictImage = useCallback(async () => {
try {
const res1 = await client.post(`/get-box/${location.state.id}`);
if (res1 && !(res1["data"]["img_id"] === -1)) {
setTaskNum(2);
const res2 = await client.post(
`/get-img/${res1["data"]["img_id"]}`
);
console.log("response 2");
if (res2) {
setTaskNum(3);
const res3 = await client.post(
`/super/${res2["data"]["historyId"]}`
);
console.log("response 3");
if (res3) {
setTaskNum(4);
const res4 = await client.post(
`/easy/${res3["data"]["historyId"]}`
);
console.log("response 4");
if (res4) {
setTaskNum(5);
const res5 = await client.post(
`/get-history/${res4["data"]["historyId"]}`
);
console.log("response 5");
if (res5) navigate("/hasil", { state: res5["data"] });
}
}
}
}
} catch (error) {
console.log(error);
setTaskNum(0);
}
}, []);
useEffect(() => {
predictImage();
}, []);
const getDescription = (num) => {
if (num === -1) {
return "Oh no...";
}
if (num === 1) {
return "Done Uplo...";
}
if (num === 2) {
return "Found the..";
}
if (num === 3) {
return "Plates are ready....";
}
if (num === 4) {
return "Eureka..";
}
if (num === 5) {
return "All Done..";
}
return "I'm sorry but..";
};
return (
<div className="container mx-auto mt-16 mb-24">
<div className="flex flex-col items-center mt-16">
<ScaleLoader
color="#3F83F8"
height={30}
speedMultiplier={0.8}
width={8}
/>
<div className="mt-6 text-xl font-medium">Doing Some Magic!</div>
<span className="mt-1 text-center font-medium text-gray-500">
{taskNum}/5 Task
<br /> {getDescription(taskNum)}
</span>
</div>
</div>
);
}
I've been trying to nest the setTaskNum
on conditional if
and function
to handle updates, but all of them always hit the API multiple times.
if (res1 && !(res1["data"]["userId"] === -1)) {
if (taskNum === 1) {
console.log("setTaskNum(2)");
setTaskNum(2);
}
const res2 = await client.get(`/users/${res1["data"]["userId"]}`);
console.log("response 2");
EDIT My real API has a long processing time, it is Machine Learning related (inference image)
Live preview of the error see in console
Upvotes: 2
Views: 108
Reputation: 21
was able to resolve it by myself,
const defaultPredictRes = {
res1: null,
res2: null,
res3: null,
res4: null,
taskNum: null
};
useState()
const [predictRes, setPredictRes] = useState(defaultPredictRes);
if (predictStep === 0) {
const apiRes1 = await client.get(`...`);
console.log("res1", apiRes1);
if (apiRes1.data)
setPredictRes((curRes) => ({
...curRes,
res1: apiRes1.data,
taskNum: 1
}));
}
if (predictStep === 1 && predictRes.res1?.userId) {
const apiRes2 = await client.get(`...`);
console.log("res2", apiRes2);
if (apiRes2)
setPredictRes((curRes) => ({
...curRes,
res2: apiRes2.data,
taskNum: 2
}));
}
// first render + first initiate, componentDidMount()
useEffect(() => {
setPredictRes((curRes) => ({ ...curRes, taskNum: 0 }));
}, []);
// next stepper
useEffect(() => {
console.log("step", predictRes);
predictImage(predictRes.taskNum);
}, [predictRes.taskNum]);
Console Result
Full Code
Upvotes: 0
Reputation: 203587
What I see is the useEffect
being run twice basically. I see only two of each log.
This seems to be caused by rendering the app into a React.StrictMode
component which double-invokes certain lifecycle methods and functions as a way to help you detect unexpected side-effects. Function component bodies are one of these. What I see missing from the code is any useEffect
hook cleanup function to cancel/abort any in-flight network requests.
You should return a cleanup function to do this.
Example:
// Moved outside component to remove it as a dependency
const client = axios.create({
baseURL: "https://jsonplaceholder.typicode.com"
});
function Loading() {
const [taskNum, setTaskNum] = useState(1);
const predictImage = useCallback(async ({ controller }) => { // <-- receive controller
try {
console.log("before res 1");
const res1 = await client.get(`/posts/1`, { signal: controller.signal }); // <-- pass controller signal
console.log(res1);
console.log("response 1");
if (res1) {
setTaskNum(2);
console.log("setTaskNum(2)");
const res2 = await client.get(`/users/${res1["data"]["userId"]}`, {
signal: controller.signal // <-- pass controller signal
});
console.log("response 2");
console.log(res2);
if (res2) {
setTaskNum(3);
console.log("setTaskNum(3)");
const res3 = await client.get(`/comments/${res2["data"]["id"]}`, {
signal: controller.signal // <-- pass controller signal
});
console.log("response 3");
console.log(res3);
}
}
} catch (error) {
console.log(error);
setTaskNum(0);
}
}, []);
useEffect(() => {
const controller = new AbortController(); // <-- create controller
predictImage({ controller }); // <-- pass controller
return () => controller.abort(); // <-- return cleanup function to abort
}, [predictImage]);
return (
...
);
}
Upvotes: 1
Reputation: 1861
The second parameter to setState() is an optional callback function that will be executed once setState is completed and the component is re-rendered.
https://reactjs.org/docs/react-component.html#setstate
So, using class components (constructor
, render
etc),
async predictImage() {
var res1 = await ...;
if(res1) {
this.setState({taskNum: 2}, ()=> {
var res2 = await ...;
if(res2) {
this.setState({taksNum: 3}, ()=> {
var res3 = await ...;
if(res3) {
this.setState({taskNum: 4}, ()=>{
var res4 = await ...;
if(res4) {
}
);
}
}
Since you're calling predictImage
from inside useEffect(..,[])
,
you can call it from inside componentDidMount
or even rename predictImage
to componentDidMount
.
If you are using React Router <= 5, useLocation
can be replaced with withRouter
.
For React Router 6+, see (https://reactrouter.com/en/v6.3.0/faq)
Upvotes: 0