atomWithDebounce
atomWithDebounce
helps with creating an atom where state set should be debounced.
This util is useful for text search inputs, where you would like to call functions in derived atoms only once after waiting for a duration, instead of firing an action on every keystroke.
import { atom, SetStateAction } from 'jotai'export default function atomWithDebounce<T>(initialValue: T,delayMilliseconds = 500,shouldDebounceOnReset = false) {const prevTimeoutAtom = atom<ReturnType<typeof setTimeout> | undefined>(undefined)// DO NOT EXPORT currentValueAtom as using this atom to set state can cause// inconsistent state between currentValueAtom and debouncedValueAtomconst _currentValueAtom = atom(initialValue)const isDebouncingAtom = atom(false)const debouncedValueAtom = atom(initialValue,(get, set, update: SetStateAction<T>) => {clearTimeout(get(prevTimeoutAtom))const prevValue = get(_currentValueAtom)const nextValue =typeof update === 'function'? (update as (prev: T) => T)(prevValue): updateconst onDebounceStart = () => {set(_currentValueAtom, nextValue)set(isDebouncingAtom, true)}const onDebounceEnd = () => {set(debouncedValueAtom, nextValue)set(isDebouncingAtom, false)}onDebounceStart()if (!shouldDebounceOnReset && nextValue === initialValue) {onDebounceEnd()return}const nextTimeoutId = setTimeout(() => {onDebounceEnd()}, delayMilliseconds)// set previous timeout atom in case it needs to get clearedset(prevTimeoutAtom, nextTimeoutId)})// exported atom setter to clear timeout if neededconst clearTimeoutAtom = atom(null, (get, set, _arg) => {clearTimeout(get(prevTimeoutAtom))set(isDebouncingAtom, false)})return {currentValueAtom: atom((get) => get(_currentValueAtom)),isDebouncingAtom,clearTimeoutAtom,debouncedValueAtom,}}
Caveat
Please note that this atom has different objectives from concurrent features in React 18 such as useTransition
and useDeferredValue
whose main aim is to prevent blocking of interaction with the page for expensive updates.
For more info, please read this github discussion https://github.com/reactwg/react-18/discussions/41 under the section titled "How is it different from setTimeout?"
Example Usage
The sandbox link below shows how we would use a derived atom to fetch state based on the value of debouncedValueAtom
.
When typing a pokemon's name in <SearchInput>
, we do not send a get request on every letter, but only after delayMilliseconds
has passed since the last text input.
This reduces the number of backend requests to the server.