# useStoreTransition
A hook (opens new window) that wraps action dispatches
in startTransition (opens new window),
marking the resulting state updates as non-urgent so they do not block
higher-priority updates such as user input. It also exposes an isPending flag
you can use to drive UI feedback while the transition is in flight.
const [addTodo, isPending] = useStoreTransition(
(actions) => actions.todos.add,
);
# Arguments
mapActions(Function, required)The function that is used to resolve the action(s) that your component requires. It receives the following argument:
actions(Object)The actions of your store.
The selector may return either a single action function or a (possibly nested) object of action functions. Function leaves are wrapped to dispatch inside
startTransition. Non-function values are passed through unchanged.
# Returns
A tuple [wrappedActions, isPending]:
wrappedActionsmirrors the shape returned bymapActions, with every function leaf replaced by a wrapper that dispatches the underlying action insidestartTransition. For thunks, the wrapper still returns the underlying promise, so you canawaitit.isPending(boolean) istruewhile React is processing the transition,falseotherwise.
# Example: single action
import { useStoreState, useStoreTransition } from 'easy-peasy';
function Counter() {
const count = useStoreState((s) => s.count);
const [increment, isPending] = useStoreTransition(
(actions) => actions.increment,
);
return (
<div>
<span>{count}</span>
<button disabled={isPending} onClick={() => increment()} type="button">
{isPending ? 'Working...' : 'Increment'}
</button>
</div>
);
}
# Example: object of actions
The hook also accepts a selector that returns a (possibly nested) object of actions — every function leaf is wrapped recursively.
import { useStoreState, useStoreTransition } from 'easy-peasy';
function TodoTools() {
const items = useStoreState((s) => s.todos.items);
const [todos, isPending] = useStoreTransition((a) => a.todos);
return (
<div>
<button onClick={() => todos.add('write tests')} type="button">Add</button>
<button onClick={() => todos.clear()} type="button">Clear</button>
{isPending && <span>Updating...</span>}
<ul>{items.map((t) => <li key={t}>{t}</li>)}</ul>
</div>
);
}
# Example: awaiting a wrapped thunk
Wrapped thunks still return the original promise, so the resolved value remains available inside the transition.
import { useStoreTransition } from 'easy-peasy';
function FetchButton() {
const [fetchItem, isPending] = useStoreTransition(
(actions) => actions.fetchItem,
);
return (
<button
disabled={isPending}
onClick={async () => {
const result = await fetchItem('one');
console.log(result);
}}
type="button"
>
{isPending ? 'Fetching...' : 'Fetch'}
</button>
);
}
# When to use this
Reach for useStoreTransition when an action triggers a re-render that is
expensive or non-urgent — for example a thunk that issues several incremental
state updates, or a sort/filter change that affects a large list. Marking the
update as a transition lets React keep the UI responsive (e.g. typing in an
input) while the heavier render is in progress.