2

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();
  }, []);

get description function

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..";
};

my render return

  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

Edit React state loop on conditional if

7
  • res1, res2, etc are response values, can you clarify what you mean by "running"? What are the steps to reproduce? Commented Sep 8, 2022 at 6:34
  • sorry if that wasn't clear enough, what I mean by running is the axios.post method that hit the API, the client.post() will do post 2 times, 1 for the first time and the second i think after the state is updated. The API will instantly get running after the page load. Commented Sep 8, 2022 at 7:50
  • Think you could create a running codesandbox demo that reproduces this issue that we could inspect and debug live? Commented Sep 8, 2022 at 9:29
  • got it live in here Commented Sep 8, 2022 at 9:35
  • The sandbox link doesn't appear to be live any more. Commented Sep 8, 2022 at 15:30

3 Answers 3

1

What I see is the useEffect being run twice basically. I see only two of each log.

enter image description here

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 (
    ...
  );
}

Edit react-state-loop-on-conditional-if

enter image description here

Sign up to request clarification or add additional context in comments.

4 Comments

yes, it's right, each API has been hit twice. I do use the React.StrictMode on the app file, should I remove it too?
had it tried on my real API it still produces multiple outputs on the same API, also based on my search the controller.abort will cancel API that takes some time to load, considering my real API has some heavy load data/ processing (ML related), is there any other possibilities to do such API chain while not running each API twice?
@NicholasNanda No, I would recommend/suggest removing the StrictMode component since it's there to help you detect unexpected side-effects and other issues in your app.
@NicholasNanda Not really, nothing off the top of my head. If you need to fetch data then you just need to do it and wait for it to complete. I don't see any way for multiple outputs on the same API to occur unless the predictImage is called more than once. Are you sure your real code is only calling the function once at-a-time?
0

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)

3 Comments

can you elaborate more about using this.setState() by using the classes, tried and found out TypeError: this.setState is not a function
I meant React Class Components.
Chaining this.setState callbacks to enqueue further state updates is a React anti-pattern. Use the componentDidUpdate lifecycle to handle this. This is similar in function to what the useEffect hook does in React function components.
0

was able to resolve it by myself,

  1. Defining global variable
const defaultPredictRes = {
  res1: null,
  res2: null,
  res3: null,
  res4: null,
  taskNum: null
};
  1. Passing it to the useState()
const [predictRes, setPredictRes] = useState(defaultPredictRes);
  1. Set the useState() within conditional
      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
          }));
      }
  1. Create 2 useEffect(), to compensate multiple API catch at first page load.
  // 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

enter image description here


Full Code

Edit React state loop on conditional if

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.