Last active
November 15, 2025 10:38
-
-
Save jasonm23/e7ea9731675fd7a9941491ba595deb59 to your computer and use it in GitHub Desktop.
Not sure this already exists, does now: Finite/Wrap Around generic history React hook. - Note this is value history so an abstraction lower than things like browser history.
This file contains hidden or 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 { useState, useCallback } from 'react'; | |
| interface History<T> { | |
| history: T[]; | |
| head: T | undefined; | |
| previous: T | undefined; | |
| next: T | undefined; | |
| addItem: (item: T) => void; | |
| previousItem: () => void; | |
| nextItem: () => void; | |
| removePrev: (n: number) => void; | |
| removeAll: () => void; | |
| } | |
| interface UseHistoryOptions<T> { | |
| maxLength: number; | |
| init?: T[]; | |
| allowDuplicates?: boolean; | |
| } | |
| export function useHistory<T>({ | |
| maxLength, | |
| init = [], | |
| allowDuplicates = true | |
| }: UseHistoryOptions<T>): History<T> { | |
| const [history, setHistory] = useState<T[]>([...init]); | |
| const [currentIndex, setCurrentIndex] = useState(init.length ? init.length - 1 : -1); | |
| const addItem = useCallback( | |
| (item: T) => { | |
| setHistory((prev) => { | |
| if (!allowDuplicates && prev[prev.length - 1] === item) return prev; | |
| const next = | |
| prev.length >= maxLength | |
| ? [...prev.slice(1), item] | |
| : [...prev, item]; | |
| setCurrentIndex(next.length - 1); | |
| return next; | |
| }); | |
| }, | |
| [allowDuplicates, maxLength] | |
| ); | |
| const previousItem = useCallback(() => { | |
| setCurrentIndex((i) => (i > 0 ? i - 1 : i)); | |
| }, []); | |
| const nextItem = useCallback(() => { | |
| setCurrentIndex((i) => (i < history.length - 1 ? i + 1 : i)); | |
| }, [history.length]); | |
| const removePrev = useCallback((n: number) => { | |
| setHistory((prev) => { | |
| const next = prev.slice(n); | |
| setCurrentIndex(next.length - 1); | |
| return next; | |
| }); | |
| }, []); | |
| const removeAll = useCallback(() => { | |
| setHistory([]); | |
| setCurrentIndex(-1); | |
| }, []); | |
| return { | |
| history, | |
| head: history[currentIndex], | |
| previous: currentIndex > 0 ? history[currentIndex - 1] : undefined, | |
| next: currentIndex >= 0 && currentIndex < history.length - 1 ? history[currentIndex + 1] : undefined, | |
| addItem, | |
| previousItem, | |
| nextItem, | |
| removePrev, | |
| removeAll | |
| }; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment