Skip to main content

Resets

There are many situations where you may want to reset the state of one or more atoms. While Zedux doesn't have any built-in atom reset helpers like Recoil or Jotai, there are many ways to go about resetting state.

you will learn:
  • Several ways to reset an atom instance's state
  • How to reset the state of multiple atom instances at once
  • How to reset an entire ecosystem

Invalidation

For atoms that don't inject their own stores, including query atoms, you can use instance.invalidate() to essentially "reset" an atom instance's state:

// this atom never reevaluates unless someone calls `instance.invalidate()`:
const todoListAtom = atom('todoList', () => [])

const TodoResetButton = () => {
const { invalidate } = useAtomInstance(todoListAtom)

return <button onClick={invalidate}>Reset</button>
}

Here's another example using the ecosystem to prevent registering a static dependency on the atom instance:

const todoListAtom = atom('todoList', () => [])

const TodoResetButton = () => {
const ecosystem = useEcosystem()

return (
<button onClick={() => ecosystem.find(todoListAtom)?.invalidate()}>
Reset
</button>
)
}

This approach falls short when the atom does inject a store, since the store persists state across evaluations.

const todoListAtom = atom('todoList', () => {
// invalidating this atom does nothing 'cause the store's state is unchanged:
const store = injectStore([])

return store
})

Exports

Atoms can export anything. This means they can export a function that selectively "resets" whatever you want:

const todoListAtom = atom('todoList', () => {
const store = injectStore([])

return api(store).setExports({
reset: () => store.setState([]),
})
})

const TodoResetButton = () => {
const { reset } = useAtomInstance(todoListAtom).exports

return <button onClick={reset}>Reset</button>
}

Here's that example expanded in a live sandbox:

Live Sandbox
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
const todoListAtom = atom('todoList', () => {
const store = injectStore<string[]>([])

const addTodo = (text: string) =>
store.setState(state => Array.from(new Set([...state, text])))

const reset = () => store.setState([])

return api(store).setExports({
addTodo,
reset,
})
})

function Controls() {
const [text, setText] = useState('')
const { addTodo, reset } = useAtomInstance(todoListAtom).exports

return (
<div>
<input onChange={event => setText(event.target.value)} value={text} />
<button
onClick={() => {
addTodo(text)
setText('')
}}
>
+ Add Todo
</button>
<button onClick={reset}>Reset</button>
</div>
)
}

function TodoList() {
const todos = useAtomValue(todoListAtom)

return (
<ul>
{todos.map(todo => (
<li key={todo}>{todo}</li>
))}
</ul>
)
}

function Todos() {
return (
<>
<TodoList />
<Controls />
</>
)
}

Force Destruction

As seen in the destruction walkthrough, every atom instance can be force-destroyed. This destroys the atom instance regardless of whether it has dependents.

If the atom instance does have dependents, those dependents will immediately recreate the instance. This resurrected instance is a completely new instance - fresh store, exports, promise, everything.

Just pass true to instance.destroy():

const todoListAtom = atom('todoList', () => [])

const TodoResetButton = () => {
// don't destructure the `destroy` AtomInstance class method:
const instance = useAtomInstance(todoListAtom)

return <button onClick={() => instance.destroy(true)}>Reset</button>
}

This is probably the most generally useful method of resetting state.

Multiple Atoms

So far we've looked at ways to reset a single atom instance. To reset several atom instances at once, there are many ways to go about it. For example:

  • Create a custom injector that hooks into global "reset" events and runs a callback.
  • Create a "resetStream" atom that exposes an RxJS observable that can be piped off of to perform resets.
  • Create an ecosystem plugin or external helper with access to the ecosystem that invalidates or force-destroys multiple instances.

Using A Stream

Here's an example using a custom injectReset injector to hook into a universal "reset stream":

// reset helpers:
const resetAtom = atom('reset', null)
const injectReset = callback => {
const resetInstance = injectAtomInstance(resetAtom)

injectEffect(() => {
const subscription = resetInstance.store.subscribe({
effects: () => callback(),
})

return () => subscription.unsubscribe()
}, [resetInstance])
}

// example usage:
const todoListAtom = atom('todoList', () => {
const store = injectStore([])

injectReset(() => store.setState([]))

return store
})

const GlobalResetButton = () => {
const instance = useAtomInstance(resetAtom)

return (
<button onClick={() => instance.dispatch({ type: 'reset' })}>Reset</button>
)
}

And here's that example expanded in a live sandbox:

Live Sandbox
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
// reset helpers:
const resetAtom = atom('reset', () => {
const store = injectStore()

const reset = () => store.dispatch({ type: 'reset' })

return api(store).setExports({ reset })
})

const injectReset = callback => {
const resetInstance = injectAtomInstance(resetAtom)

injectEffect(() => {
const subscription = resetInstance.store.subscribe({
effects: () => callback(),
})

return () => subscription.unsubscribe()
}, [resetInstance])
}

// todos app:
const todoListAtom = atom('todoList', () => {
const store = injectStore<string[]>([])

const addTodo = (text: string) =>
store.setState(state => Array.from(new Set([...state, text])))

injectReset(() => store.setState([]))

return api(store).setExports({ addTodo })
})

function Controls() {
const [text, setText] = useState('')
const { addTodo } = useAtomInstance(todoListAtom).exports
const { reset } = useAtomInstance(resetAtom).exports

return (
<div>
<input onChange={event => setText(event.target.value)} value={text} />
<button
onClick={() => {
addTodo(text)
setText('')
}}
>
+ Add Todo
</button>
<button onClick={reset}>Reset</button>
</div>
)
}

function TodoList() {
const todos = useAtomValue(todoListAtom)

return (
<ul>
{todos.map(todo => (
<li key={todo}>{todo}</li>
))}
</ul>
)
}

function Todos() {
return (
<>
<TodoList />
<Controls />
</>
)
}

Ecosystem Resets

To surgically reset the state of every atom instance in the ecosystem, you'd have to follow one of the above approaches. But for a basic global reset, you can use ecosystem.reset().

A single ecosystem.reset() call will:

  • Force-destroy every atom instance and cached atom selector in the ecosystem.
  • Run the previous onReady function's cleanup function (if any).
  • Rerun the onReady function (if any).
  • Allow all external dependents (e.g. React components) to recreate their dependencies. These are the "leaf nodes" of the graph.
  • The leaf nodes will recreate their own dependencies and so on up the graph tree.

The final result should contain every non-stale atom instance and every cached selector that existed before the reset, but of course as freshly initialized new references.

Live Sandbox
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
const todoListAtom = atom('todoList', () => {
const store = injectStore<string[]>([])

const addTodo = (text: string) =>
store.setState(state => Array.from(new Set([...state, text])))

return api(store).setExports({ addTodo })
})

function Controls() {
const [text, setText] = useState('')
const { addTodo } = useAtomInstance(todoListAtom).exports
// just grab the ecosystem:
const ecosystem = useEcosystem()

return (
<div>
<input onChange={event => setText(event.target.value)} value={text} />
<button
onClick={() => {
addTodo(text)
setText('')
}}
>
+ Add Todo
</button>
<button onClick={() => ecosystem.reset()}>Reset</button>
</div>
)
}

function TodoList() {
const todos = useAtomValue(todoListAtom)

return (
<ul>
{todos.map(todo => (
<li key={todo}>{todo}</li>
))}
</ul>
)
}

function Todos() {
return (
<>
<TodoList />
<Controls />
</>
)
}

The ecosystem can be reset anywhere - whether above or below an EcosystemProvider or completely outside React.

tip

While ecosystem.reset() is incredibly effective, you probably won't find yourself using it in most apps. An approach like the injectReset() example adds more boilerplate, but is more powerful.

Recap

  • Use instance.invalidate() to "reset" atoms that don't use stores in their state factory.
  • Use instance.destroy(true) to "reset" any atom by fully destroying and recreating it.
  • Use atom exports to gain more control over precisely what is reset.
  • Hook into another atom or external stream for the most control.
  • Reset an entire ecosystem with ecosystem.reset()

Next Steps

That's about it for the key features of Zedux. Be thou crowned king of the state. You're ready to jump in and get building! Check out the advanced guides, addon packages like @zedux/immer and @zedux/machines, and the API docs as needed. Happy coding!