UNPKG

1.8 kBJavaScriptView Raw
1import { useCallback, useEffect, useRef, useState } from 'react';
2/**
3 * A hook that mirrors `useState` in function and API, expect that setState
4 * calls return a promise that resolves after the state has been set (in an effect).
5 *
6 * This is _similar_ to the second callback in classy setState calls, but fires later.
7 *
8 * ```ts
9 * const [counter, setState] = useStateAsync(1);
10 *
11 * const handleIncrement = async () => {
12 * await setState(2);
13 * doWorkRequiringCurrentState()
14 * }
15 * ```
16 *
17 * @param initialState initialize with some state value same as `useState`
18 */
19export default function useStateAsync(initialState) {
20 const [state, setState] = useState(initialState);
21 const resolvers = useRef([]);
22 useEffect(() => {
23 resolvers.current.forEach(resolve => resolve(state));
24 resolvers.current.length = 0;
25 }, [state]);
26 const setStateAsync = useCallback(update => {
27 return new Promise((resolve, reject) => {
28 setState(prevState => {
29 try {
30 let nextState;
31 // ugly instanceof for typescript
32 if (update instanceof Function) {
33 nextState = update(prevState);
34 } else {
35 nextState = update;
36 }
37
38 // If state does not change, we must resolve the promise because
39 // react won't re-render and effect will not resolve. If there are already
40 // resolvers queued, then it should be safe to assume an update will happen
41 if (!resolvers.current.length && Object.is(nextState, prevState)) {
42 resolve(nextState);
43 } else {
44 resolvers.current.push(resolve);
45 }
46 return nextState;
47 } catch (e) {
48 reject(e);
49 throw e;
50 }
51 });
52 });
53 }, [setState]);
54 return [state, setStateAsync];
55}
\No newline at end of file