UNPKG

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