1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| import { useCallback, useReducer } from 'react'
const UNDO = 'UNDO' const REDO = 'REDO' const SET = 'SET' const RESET = 'RESET'
type State<T> = { past: T[] present: T future: T[] }
type Action<T> = { newPresent?: T type: typeof UNDO | typeof REDO | typeof SET | typeof RESET }
const undoReducer = <T>(state: State<T>, action: Action<T>) => { const { past, present, future } = state const { type, newPresent } = action
switch (type) { case UNDO: { if (past.length === 0) return state const previous = past[past.length - 1] const newPast = past.slice(0, past.length - 1) return { past: newPast, present: previous, future: [present, ...future], } } case REDO: { if (future.length === 0) return state const next = future[0] const newFuture = future.slice(1) return { past: [...past, present], present: next, future: newFuture, } } case SET: { if (newPresent === present) return state return { past: [...past, present], present: newPresent, future: [], } } case RESET: { return { past: [], present: newPresent, future: [], } } default: { return state } } }
export const useUndo = <T>(initialPresent: T) => { const [state, dispatch] = useReducer(undoReducer, { past: [], present: initialPresent, future: [], } as State<T>)
const canUndo = state.past.length !== 0 const canRedo = state.future.length !== 0
const undo = useCallback(() => dispatch({ type: UNDO }), [])
const redo = useCallback(() => dispatch({ type: REDO }), [])
const set = useCallback((newPresent: T) => dispatch({ newPresent, type: SET }), [])
const reset = useCallback((newPresent: T) => dispatch({ newPresent, type: RESET }), [])
return [state, { set, reset, undo, redo, canUndo, canRedo }] as const }
const useSafeDispatch = <T>(dispatch: (...args: T[]) => void) => { const mountedRef = useMountedRef() return (...args: T[]) => (mountedRef.current ? dispatch(...args) : void 0) }
|