Encapsulate reusable state patterns in hooks.
useToggle
jsxfunction useToggle(initialValue = false) { const [value, setValue] = useState(initialValue); const toggle = useCallback(() => setValue(v => !v), []); const setTrue = useCallback(() => setValue(true), []); const setFalse = useCallback(() => setValue(false), []); return [value, { toggle, setTrue, setFalse }]; } // Usage function Modal() { const [isOpen, { toggle, setFalse }] = useToggle(); return ( <> <button onClick={toggle}>Open</button> {isOpen && <Dialog onClose={setFalse} />} </> ); }
useList
jsxfunction useList(initialList = []) { const [list, setList] = useState(initialList); const push = useCallback((item) => { setList(l => [...l, item]); }, []); const removeAt = useCallback((index) => { setList(l => l.filter((_, i) => i !== index)); }, []); const removeById = useCallback((id) => { setList(l => l.filter(item => item.id !== id)); }, []); const updateAt = useCallback((index, newItem) => { setList(l => l.map((item, i) => i === index ? newItem : item)); }, []); const clear = useCallback(() => setList([]), []); return [list, { push, removeAt, removeById, updateAt, clear, set: setList }]; } // Usage function TodoList() { const [todos, { push, removeById }] = useList([]); const addTodo = (text) => { push({ id: Date.now(), text, completed: false }); }; return ( <ul> {todos.map(todo => ( <li key={todo.id}> {todo.text} <button onClick={() => removeById(todo.id)}>×</button> </li> ))} </ul> ); }
useMap (Object State)
jsxfunction useMap(initialMap = {}) { const [map, setMap] = useState(initialMap); const set = useCallback((key, value) => { setMap(m => ({ ...m, [key]: value })); }, []); const remove = useCallback((key) => { setMap(m => { const { [key]: _, ...rest } = m; return rest; }); }, []); const reset = useCallback(() => setMap(initialMap), [initialMap]); return [map, { set, remove, reset, setAll: setMap }]; }
useHistory (Undo/Redo)
jsxfunction useHistory(initialState) { const [history, setHistory] = useState({ past: [], present: initialState, future: [], }); const canUndo = history.past.length > 0; const canRedo = history.future.length > 0; const set = useCallback((newState) => { setHistory(h => ({ past: [...h.past, h.present], present: typeof newState === 'function' ? newState(h.present) : newState, future: [], })); }, []); const undo = useCallback(() => { setHistory(h => { if (h.past.length === 0) return h; const previous = h.past[h.past.length - 1]; const newPast = h.past.slice(0, -1); return { past: newPast, present: previous, future: [h.present, ...h.future], }; }); }, []); const redo = useCallback(() => { setHistory(h => { if (h.future.length === 0) return h; const next = h.future[0]; const newFuture = h.future.slice(1); return { past: [...h.past, h.present], present: next, future: newFuture, }; }); }, []); return [history.present, { set, undo, redo, canUndo, canRedo }]; } // Usage function Editor() { const [text, { set, undo, redo, canUndo, canRedo }] = useHistory(''); return ( <div> <textarea value={text} onChange={e => set(e.target.value)} /> <button onClick={undo} disabled={!canUndo}>Undo</button> <button onClick={redo} disabled={!canRedo}>Redo</button> </div> ); }