Created
November 19, 2021 20:38
-
-
Save clintandrewhall/3cf704bc4db03a913c38d1a9c0ead6fa to your computer and use it in GitHub Desktop.
React Hook - undo/redo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useReducer, useCallback, Reducer } from 'react'; | |
interface State<T = string> { | |
past: T[]; | |
present: T; | |
future: T[]; | |
} | |
interface Action<T> { | |
type: 'UNDO' | 'REDO' | 'SET' | 'CLEAR'; | |
value?: T; | |
} | |
type UndoRedoReducer<T> = Reducer<State<T>, Action<T>>; | |
function getInitialState<T>(initialValue: T): State<T> { | |
return { | |
past: [], | |
present: initialValue, | |
future: [], | |
}; | |
} | |
// Our reducer function to handle state changes based on action | |
function getReducer<T>(initialState: State<T>): UndoRedoReducer<T> { | |
return (state, action) => { | |
const { past, present, future } = state; | |
let result: State<T> = { ...state }; | |
switch (action.type) { | |
case 'UNDO': | |
result = { | |
past: past.slice(0, past.length - 1), | |
present: past[past.length - 1], | |
future: [present, ...future], | |
}; | |
case 'REDO': | |
result = { | |
past: [...past, present], | |
present: future[0], | |
future: future.slice(1), | |
}; | |
case 'SET': | |
const { value } = action; | |
if (value === present || !value) { | |
return state; | |
} | |
result = { | |
past: [...past, present], | |
present: value, | |
future: [], | |
}; | |
case 'CLEAR': | |
result = { | |
...initialState, | |
}; | |
} | |
return result; | |
}; | |
} | |
export function useUndoRedo<T>(value: T) { | |
const [state, dispatch] = useReducer(getReducer(getInitialState(value)), getInitialState(value)); | |
const canUndo = state.past.length !== 0; | |
const canRedo = state.future.length !== 0; | |
const undo = useCallback(() => { | |
if (canUndo) { | |
dispatch({ type: 'UNDO' }); | |
} | |
}, [canUndo, dispatch]); | |
const redo = useCallback(() => { | |
if (canRedo) { | |
dispatch({ type: 'REDO' }); | |
} | |
}, [canRedo, dispatch]); | |
const set = useCallback( | |
(newPresent: T) => dispatch({ type: 'SET', value: newPresent }), | |
[dispatch] | |
); | |
const clear = useCallback(() => dispatch({ type: 'CLEAR' }), [dispatch]); | |
return { state: state.present, set, undo, redo, clear, canUndo, canRedo }; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment