Skip to content

Instantly share code, notes, and snippets.

@clintandrewhall
Created November 19, 2021 20:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save clintandrewhall/3cf704bc4db03a913c38d1a9c0ead6fa to your computer and use it in GitHub Desktop.
Save clintandrewhall/3cf704bc4db03a913c38d1a9c0ead6fa to your computer and use it in GitHub Desktop.
React Hook - undo/redo
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