A Reset Button That Doesn’t Lose Its Place
While building downshift, a common need emerged: users wanted to reset the dropdown to its initial state — no input, nothing highlighted, no selection, closed — at any time. But “initial state” wasn’t always the same. Some users wanted a default input value, others wanted a preselected item or an open dropdown.
The state initializer pattern solves this by giving consumers an explicit API to reset a component to the state it had when it first mounted, without forcing a full unmount and remount. In many ways it mirrors how defaultValue works on an HTML input: you set it once, and it only matters at the beginning.
function Counter() {
const [count, setCount] = React.useState(0)
const increment = () => setCount((c) => c + 1)
const reset = () => setCount(0)
return (
<div>
<button onClick={increment}>{count}</button>
<button onClick={reset}>Reset</button>
</div>
)
}
Here the component initializes its counter to 0 and exposes a way to reset back to that value. The pattern extends naturally to let consumers set the initial value themselves:
<Counter initialCount={1} />
Supporting that initialCount prop takes only a small change:
function Counter({ initialCount = 0 }: { initialCount?: number }) {
// ^^^ accept the prop with a default value so it's optional
const [count, setCount] = React.useState(initialCount) // <-- pass it to your state
const increment = () => setCount((c) => c + 1)
const reset = () => setCount(initialCount) // <-- pass that initialCount value to the reset function
return (
<div>
<button onClick={increment}>{count}</button>
<button onClick={reset}>Reset</button>
</div>
)
}
That covers the core idea, but there’s an edge case worth handling. What if the consumer changes initialCount after the component has mounted? That would undermine the meaning of “initial” — resetting would no longer return the component to its true starting point. It’s possible to defend against this simply, without resorting to effects or mounted flags:
const { current: initialState } = React.useRef({ count: initialCount })
const [initialState] = React.useState({ count: initialCount })
const [initialState] = React.useReducer((s) => s, { count: initialCount })
// actual initial count is: initialState.count
Among those options, a useRef is the most straightforward. With that in place, changing initialCount after mount has no effect on the reset behavior.
The Built-In Alternative: key
React already gives you a way to fully reinitialize any component: the key prop. Assign a new value to key and React will unmount and remount the component from scratch.
function KeyPropReset() {
const [key, setKey] = React.useState(0)
const resetCounter = () => setKey((k) => k + 1)
return <KeyPropResetCounter key={key} reset={resetCounter} />
}
function KeyPropResetCounter({ reset }) {
const [count, setCount] = React.useState(0)
const increment = () => setCount((c) => c + 1)
return <CountUI count={count} increment={increment} reset={reset} />
}
That approach works, but it carries consequences beyond a simple state reset. Remounting triggers useEffect cleanups and re-runs callbacks, which may or may not be desirable. It can also disrupt UI details that depend on the component staying alive. For example, in a state-change animation demo, resetting via key produces no animation, while the state initializer approach keeps the animation intact. Restructuring your code just to host a key change isn’t always practical either.
A Simple, Consistent API
The state initializer pattern is easy to implement and gives users a predictable hook for reinitializing state. That’s why it earned its place back in Advanced React Patterns; the problems it solves kept coming up in practice. It’s a small abstraction, but one that keeps your component’s reset behavior explicit and controllable.



