useStateTransition.ts37 lines · main
1import { useEffect, useRef } from 'react'
2
3export function useStateTransition<
4 State extends { type: string },
5 PrevType extends State['type'],
6 NewType extends State['type'],
7>(
8 state: State,
9 // Documentary only — the entry-detection logic below intentionally does not
10 // require `savedPrevState.type === prevTest`. React 18+ auto-batches
11 // dispatches across awaits (e.g. dispatch SUBMIT → await → mutation
12 // onError dispatch ERROR), which collapses `prevTest → newTest` into a
13 // single render where the intermediate `prevTest` state is never observed,
14 // so the previous state may be any variant other than `newTest`. `cb`'s
15 // first parameter is typed accordingly.
16 _prevTest: PrevType,
17 newTest: NewType,
18 cb: (
19 prevState: Exclude<State, { type: NewType }>,
20 currState: Extract<State, { type: NewType }>
21 ) => void
22): void {
23 const prevState = useRef(state)
24
25 useEffect(() => {
26 const savedPrevState = prevState.current
27
28 if (savedPrevState.type !== newTest && state.type === newTest) {
29 cb(
30 savedPrevState as Exclude<State, { type: NewType }>,
31 state as Extract<State, { type: NewType }>
32 )
33 }
34
35 prevState.current = state
36 }, [cb, newTest, state])
37}