(defaultValue: S | (() => S))
| 20 | // layout effect, then continues the generator. This allows sequential updates to state to be |
| 21 | // written linearly. |
| 22 | export function useValueEffect<S>(defaultValue: S | (() => S)): [S, Dispatch<SetValueAction<S>>] { |
| 23 | let [value, setValue] = useState(defaultValue); |
| 24 | // Keep an up to date copy of value in a ref so we can access the current value in the generator. |
| 25 | // This allows us to maintain a stable queue function. |
| 26 | let currValue = useRef(value); |
| 27 | let effect: RefObject<Generator<S> | null> = useRef<Generator<S> | null>(null); |
| 28 | |
| 29 | // Store the function in a ref so we can always access the current version |
| 30 | // which has the proper `value` in scope. |
| 31 | let nextRef = useRef(() => { |
| 32 | if (!effect.current) { |
| 33 | return; |
| 34 | } |
| 35 | // Run the generator to the next yield. |
| 36 | let newValue = effect.current.next(); |
| 37 | |
| 38 | // If the generator is done, reset the effect. |
| 39 | if (newValue.done) { |
| 40 | effect.current = null; |
| 41 | return; |
| 42 | } |
| 43 | |
| 44 | // If the value is the same as the current value, |
| 45 | // then continue to the next yield. Otherwise, |
| 46 | // set the value in state and wait for the next layout effect. |
| 47 | if (currValue.current === newValue.value) { |
| 48 | nextRef.current(); |
| 49 | } else { |
| 50 | setValue(newValue.value); |
| 51 | } |
| 52 | }); |
| 53 | |
| 54 | useLayoutEffect(() => { |
| 55 | currValue.current = value; |
| 56 | // If there is an effect currently running, continue to the next yield. |
| 57 | if (effect.current) { |
| 58 | nextRef.current(); |
| 59 | } |
| 60 | }); |
| 61 | |
| 62 | let queue = useCallback( |
| 63 | fn => { |
| 64 | effect.current = fn(currValue.current); |
| 65 | nextRef.current(); |
| 66 | }, |
| 67 | [nextRef] |
| 68 | ); |
| 69 | |
| 70 | return [value, queue]; |
| 71 | } |
no test coverage detected