Home > other >  Using useState of complex object not working as expectd in ReactJS
Using useState of complex object not working as expectd in ReactJS

Time:11-25

I have a functional 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.

CodePudding user response:

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