useStateTransition.ts 1.2 KB

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