Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Executing async code on update of state with react-hooks

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?

like image 600
Colin Ricardo Avatar asked Dec 22 '18 19:12

Colin Ricardo


People also ask

Is React State update asynchronous?

ReactJs sets its state asynchronously because it can result in an expensive operation. Making it synchronous might leave the browser unresponsive. Asynchronous setState calls are batched to provide a better user experience and performance.

How do you update state immediately in React hooks?

To update state in React components, we'll use either the this. setState function or the updater function returned by the React. useState() Hook in class and function components, respectively.

Are React State Hooks async?

React useState hook is asynchronous!

How wait for state changes in React hooks?

Use the useEffect hook to wait for state to update in React. You can add the state variables you want to track to the hook's dependencies array and the function you pass to useEffect will run every time the state variables change.


1 Answers

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);   }; 
like image 115
Shubham Khatri Avatar answered Sep 30 '22 19:09

Shubham Khatri