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?