# useStoreOptimistic
A hook (opens new window) that combines
useStoreState with React's
useOptimistic (opens new window), allowing
components to render an optimistic value while a pending action is in flight.
Once the underlying store state changes (typically when the action commits),
the optimistic value is discarded and the real value is shown.
const [items, addOptimistic] = useStoreOptimistic(
(state) => state.todos.items,
(current, pending) => [...current, pending],
);
# Arguments
mapState(Function, required)The function that is used to resolve the piece of state that should be shown optimistically. It receives the following argument:
state(Object)The state of your store.
updateFn(Function, required)A reducer that produces the optimistic value from the current state and a pending value. It receives the following arguments:
current(any)The current selected state — either the most recent value from the store, or the most recent optimistic value if one is in flight.
pending(any)The optimistic value that was passed to the dispatch function (the second tuple member returned by the hook).
# Returns
A tuple [optimisticState, addOptimistic]:
optimisticStateis the result of the most recentupdateFncall while a transition is pending, otherwise the real value from the store.addOptimistic(pending)schedules an optimistic update. It must be called inside a transition (e.g. from withinstartTransition(opens new window) or a form action (opens new window)). The optimistic value is shown until the underlying store state next changes, at which point it resets to reflect the real store value.
# Example
import { startTransition } from 'react';
import {
useStoreActions,
useStoreOptimistic,
} from 'easy-peasy';
function TodoList() {
const [items, addOptimistic] = useStoreOptimistic(
(state) => state.todos.items,
(current, pending) => [...current, { ...pending, pending: true }],
);
const addItemAsync = useStoreActions((a) => a.todos.addItemAsync);
return (
<div>
<ul>
{items.map((item) => (
<li key={item.id} style={{ opacity: item.pending ? 0.5 : 1 }}>
{item.text}
</li>
))}
</ul>
<button
onClick={() => {
startTransition(async () => {
const draft = { id: crypto.randomUUID(), text: 'New todo' };
addOptimistic(draft);
await addItemAsync(draft);
});
}}
type="button"
>
Add Todo
</button>
</div>
);
}
In this example the new todo appears in the list immediately at half opacity
while addItemAsync is in flight. When the underlying state updates with the
real item, the optimistic value resets and the committed item is shown at full
opacity.
# When to use this
Reach for useStoreOptimistic when an asynchronous thunk
will eventually update the store and you want to give the user immediate
feedback before the action commits — for example, message sends, list inserts,
toggles, or any "fire-and-display" interaction. Pair it with
useStoreTransition or startTransition
to keep the UI responsive.
Note: because the optimistic value is reset when the underlying selected state changes, choose your selector carefully — selecting the smallest piece of state that the optimistic update affects gives the cleanest behaviour.