DEV Community

Cover image for React Custom Hook: useStateWithHistory
Sergey Leschev
Sergey Leschev

Posted on

React Custom Hook: useStateWithHistory

In this article series, we embark on a journey through the realm of custom React hooks, discovering their immense potential for elevating your development projects. Our focus today is on the "useStateWithHistory" hook, one of the many carefully crafted hooks available in the collection of React custom hooks.

Github: https://github.com/sergeyleschev/react-custom-hooks

import { useCallback, useRef, useState } from "react"

export default function useStateWithHistory(
    defaultValue,
    { capacity = 10 } = {}
) {
    const [value, setValue] = useState(defaultValue)
    const historyRef = useRef([value])
    const pointerRef = useRef(0)
    const set = useCallback(
        v => {
            const resolvedValue = typeof v === "function" ? v(value) : v
            if (historyRef.current[pointerRef.current] !== resolvedValue) {
                if (pointerRef.current < historyRef.current.length - 1) {
                    historyRef.current.splice(pointerRef.current + 1)
                }
                historyRef.current.push(resolvedValue)
                while (historyRef.current.length > capacity) {
                    historyRef.current.shift()
                }
                pointerRef.current = historyRef.current.length - 1
            }
            setValue(resolvedValue)
        },
        [capacity, value]
    )
    const back = useCallback(() => {
        if (pointerRef.current <= 0) return
        pointerRef.current--
        setValue(historyRef.current[pointerRef.current])
    }, [])
    const forward = useCallback(() => {
        if (pointerRef.current >= historyRef.current.length - 1) return
        pointerRef.current++
        setValue(historyRef.current[pointerRef.current])
    }, [])
    const go = useCallback(index => {
        if (index < 0 || index > historyRef.current.length - 1) return
        pointerRef.current = index
        setValue(historyRef.current[pointerRef.current])
    }, [])
    return [
        value,
        set,
        {
            history: historyRef.current,
            pointer: pointerRef.current,
            back,
            forward,
            go,
        },
    ]
}
Enter fullscreen mode Exit fullscreen mode

Advantages of useStateWithHistory:

  1. Automatic history tracking: useStateWithHistory automatically keeps track of the values you set, allowing you to access the complete history whenever you need it.

  2. Efficient memory usage: The hook utilizes a capacity parameter, ensuring that the history doesn't grow indefinitely. You can define the maximum number of historical values to keep, preventing excessive memory consumption.

  3. Time-travel functionality: With back(), forward(), and go() functions, you can seamlessly navigate through the recorded history. Travel back and forth between previous states or jump directly to a specific index, enabling powerful undo/redo or step-by-step functionality.

Where to use useStateWithHistory:

  1. Form management: Simplify the process of handling form inputs by providing an easy way to track changes, revert to previous values, or redo modifications.

  2. Undo/Redo functionality: Implement undo/redo functionality in your application with ease. Track state changes and allow users to navigate back and forth through their actions effortlessly.

  3. Step-by-step navigation: Use useStateWithHistory to build interactive guides or tutorials where users can navigate between different steps while preserving their progress.

import { useState } from "react"
import useStateWithHistory from "./useStateWithHistory"

export default function StateWithHistoryComponent() {
    const [count, setCount, { history, pointer, back, forward, go }] =
        useStateWithHistory(1)
    const [name, setName] = useState("Sergey")
    return (
        <div>
            <div>{count}</div>
            <div>{history.join(", ")}</div>
            <div>Pointer - {pointer}</div>
            <div>{name}</div>
            <button onClick={() => setCount(currentCount => currentCount * 2)}>
                Double
            </button>
            <button onClick={() => setCount(currentCount => currentCount + 1)}>
                Increment
            </button>
            <button onClick={back}>Back</button>
            <button onClick={forward}>Forward</button>
            <button onClick={() => go(2)}>Go To Index 2</button>
            <button onClick={() => setName("John")}>Change Name</button>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

Throughout this article series, we focused on one of the gems from the collection of React custom hooks – "useStateWithHistory". This hook, sourced from the "react-custom-hooks" repository, revolutionizes how we work in our React applications.

Full Version | React Custom Hooks:
https://dev.to/sergeyleschev/supercharge-your-react-projects-with-custom-hooks-pl4

Top comments (0)