Skip to content

Instantly share code, notes, and snippets.

@sekoyo
Created March 17, 2022 20:59
Show Gist options
  • Save sekoyo/e142599aa8050fd8339d9bbb56c87556 to your computer and use it in GitHub Desktop.
Save sekoyo/e142599aa8050fd8339d9bbb56c87556 to your computer and use it in GitHub Desktop.
A react hook for throttled state that doesn't suck
import { useState, useRef, useEffect, useCallback } from 'react'
export type StateSetter<S> = (intermediateState: S) => S
// For optimal performance mutate state in setIntermediateState
// and pass a custom `makeNewState` for when the final state is
// updated. e.g. `obj => {...obj}` or `arr => [...arr]`
export function useThrottledState<S>(
initialState: S | (() => S),
timeout = 300,
makeNewState: (s: S) => S = s => s
): [S, (setter: StateSetter<S>) => void] {
const [finalState, setState] = useState(initialState)
const intermediateState = useRef(finalState)
const timeoutId = useRef<ReturnType<typeof setTimeout>>()
const updateState = useCallback(() => {
setState(makeNewState(intermediateState.current))
timeoutId.current = undefined
}, [])
const setIntermediateState = useCallback(
(setter: StateSetter<S>) => {
intermediateState.current = setter(intermediateState.current)
if (!timeoutId.current) {
timeoutId.current = setTimeout(updateState, timeout)
}
},
[timeout, updateState]
)
// Cleanup if unmounted.
useEffect(
() => () => {
if (timeoutId.current) {
clearTimeout(timeoutId.current)
}
},
[]
)
return [finalState, setIntermediateState]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment