Executing async code on update of state with react-hooks

callback, javascript, react-hooks, reactjs

Solution

`useState` setter doesn't provide a callback after state update is done like setState does in React class components. In order to replicate the same behaviour, you can make use of the a similar pattern like `componentDidUpdate` lifecycle method in React class components with `useEffect` using Hooks

`useEffect` hooks takes the second parameter as an array of values which React needs to monitor for change after the render cycle is complete.

const [loading, setLoading] = useState(false);

...

useEffect(() => {
    doSomething(); // This is be executed when `loading` state changes
}, [loading])
setLoading(true);

EDIT

Unlike `setState`, the updater for `useState` hook doesn't have a callback, but you can always use a `useEffect` to replicate the above behaviour. However you need to determine the loading change

The functional approach to your code would look like

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}
const prevLoading = usePrevious(isLoading);

useEffect(() => {
   if (!prevLoading && isLoading) {
       getOrders({
          page: page + 1,
          query: localQuery,
          held: localHoldMode,
          statuses: filterMap[filterBy],
      })
      .then((o) => {
        const { orders: fetchedOrders } = o.data;
        const allOrders = orders.concat(fetchedOrders);

        setOrders(allOrders);
        setPage(page + 1);
        setPagesSeen([...pagesSeen, page + 1]);
        setPrefetchedOrders(null);
        setIsLoading(false);
      })
      .catch(e => console.error(e.message));
   }
}, [isLoading, preFetchedOrders, orders, page, pagesSeen]);

const getNextPage = () => {
    // This will scroll back to the top, and also trigger the prefetch for the next page on the way up.
    goToTop();

    if (pagesSeen.includes(page + 1)) {
      return setPage(page + 1);
    }

    if (prefetchedOrders) {
      const allOrders = orders.concat(prefetchedOrders);
      setOrders(allOrders);
      setPage(page + 1);
      setPagesSeen([...pagesSeen, page + 1]);
      setPrefetchedOrders(null);
      return;
    }

    setIsLoading(true);
  };

Problem

I have something like: ``` const [loading, setLoading] = useState(false); ... setLoading(true); doSomething(); // <--- when here, loading is still false. ``` Setting state is still async, so what's the best way to wait for this `setLoading()` call to be finished? The `setLoading()` doesn't seem to accept a callback like `setState()` used to. an example class-based ``` getNextPage = () => { // This will scroll back to the top, and also trigger the prefetch for the next page on the way up. goToTop(); if (this.state.pagesSeen.includes(this.state.page + 1)) { return this.setState({ page: this.state.page + 1, }); } if (this.state.prefetchedOrders) { const allOrders = this.state.orders.concat(this.state.prefetchedOrders); return this.setState({ orders: allOrders, page: this.state.page + 1, pagesSeen: [...this.state.pagesSeen, this.state.page + 1], prefetchedOrders: null, }); } this.setState( { isLoading: true, }, () => { getOrders({ page: this.state.page + 1, query: this.state.query, held: this.state.holdMode, statuses: filterMap[this.state.filterBy], }) .then((o) => { const { orders } = o.data; const allOrders = this.state.orders.concat(orders); this.setState({ orders: allOrders, isLoading: false, page: this.state.page + 1, pagesSeen: [...this.state.pagesSeen, this.state.page + 1], // Just in case we're in the middle of a prefetch. prefetchedOrders: null, }); }) .catch(e => console.error(e.message)); }, ); }; ``` convert to function-based ``` const getNextPage = () => { // This will scroll back to the top, and also trigger the prefetch for the next page on the way up. goToTop(); if (pagesSeen.includes(page + 1)) { return setPage(page + 1); } if (prefetchedOrders) { const allOrders = orders.concat(prefetchedOrders); setOrders(allOrders); setPage(page + 1); setPagesSeen([...pagesSeen, page + 1]); setPrefetchedOrders(null); return; } setIsLoading(true); getOrders({ page: page + 1, query: localQuery, held: localHoldMode, statuses: filterMap[filterBy], }) .then((o) => { const { orders: fetchedOrders } = o.data; const allOrders = orders.concat(fetchedOrders); setOrders(allOrders); setPage(page + 1); setPagesSeen([...pagesSeen, page + 1]); setPrefetchedOrders(null); setIsLoading(false); }) .catch(e => console.error(e.message)); }; ``` In the above, we want to run each setWhatever call sequentially. Does this mean we need to set up many different useEffect hooks to replicate this behavior?

Original source