2

I have a function component and I am declaring a useState for a complex object like this:

    const [order, setOrder] = useState<IMasterState>({
        DataInterface: null,
        ErrorMsg: "",
        IsRetrieving: true,
        RetrievingMsg: "Fetching your order status..."
    });

I now try to set the state of the order by calling setOrder in a useEffect like this:

    useEffect(() => {
        (async function() {
            let dh = new DataInterface("some string");
            let errMsg = "";
            
            // Get the sales order.
            try
            {
                await dh.FetchOrder();
            }
            catch(error: any)
            {
                errMsg = error;
            };
            
            setOrder(salesOrder => ({...salesOrder, IsRetrieving: false, ErrorMsg: errMsg, DataInterface: dh}));
        })();
    }, []);

As is, this seems to work fine. However, I have a setInterval object that changes the screen message while order.IsRetrieving is true:

    const [fetchCheckerCounter, setFetchCheckerCount] = useState<number>(0);

    const statusFetcherWatcher = setInterval(() => {
        if (order.IsRetrieving)
        {
            if (fetchCheckerCounter === 1)
            {
                setOrder(salesOrder => ({...salesOrder, RetrievingMsg: "This can take a few seconds..."}));
            }
            else if (fetchCheckerCounter === 2)
            {
                setOrder(salesOrder => ({...salesOrder, RetrievingMsg: "Almost there!.."}));
            }

            setFetchCheckerCount(fetchCheckerCounter + 1);
        }
        else
        {
            // Remove timer.
            clearInterval(statusFetcherWatcher);
        }
    }, 7000);

The issue is that order.IsRetrieving is always true for that code block, even though it does change to false, and my website changes to reflect that, even showing the data from dh.FetchOrder(). That means my timer goes on an infinite loop in the background.

So am I setting the state of order correctly? It's incredibly difficult to find a definite answer on the net, since all the answers are invariably about adding a new item to an array.

1
  • don't call setInterval() during the render phase, that's the first problem. Commented Nov 25, 2021 at 3:03

1 Answer 1

2

Issues

  1. You are setting the interval as an unintentional side-effect in the function body.
  2. You have closed over the initial order.isRetreiving state value in the interval callback.

Solution

Use a mounting useEffect to start the interval and use a React ref to cache the state value when it updates so the current value can be accessed in asynchronous callbacks.

const [order, setOrder] = useState<IMasterState>({
  DataInterface: null,
  ErrorMsg: "",
  IsRetrieving: true,
  RetrievingMsg: "Fetching your order status..."
});

const orderRef = useRef(order);

useEffect(() => {
  orderRef.current = order;
}, [order]);

useEffect(() => {
  const statusFetcherWatcher = setInterval(() => {
    if (orderRef.current.IsRetrieving) {
      if (fetchCheckerCounter === 1) {
        setOrder(salesOrder => ({
          ...salesOrder,
          RetrievingMsg: "This can take a few seconds...",
        }));
      } else if (fetchCheckerCounter === 2) {
        setOrder(salesOrder => ({
          ...salesOrder,
          RetrievingMsg: "Almost there!..",
        }));
      }

      setFetchCheckerCount(counter => counter + 1);
    } else {
      // Remove timer.
      clearInterval(statusFetcherWatcher);
    }
  }, 7000);

  return () => clearInterval(statusFetcherWatcher);
}, []);
Sign up to request clarification or add additional context in comments.

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.