Yes, reducers are pure functions that run synchronously, so any single dispatched action is processed before the next.
This may help you better understand reducer functions: https://redux.js.org/basics/data-flow
The functional component body is completely synchronous, so everything is process in the order it is called per render cycle. The link is from redux, but the reducers work the same, and are interchangeable, i.e. reducer function has signature (state, action) => nextState
.
All hook values work between render cycles, meaning, all the state updates and dispatched actions "queued" up during a render cycle are all batch processed in that order.
Given:
dispatch('a')
dispatch('b')
The dispatch('b')
reducer case will run after dispatch('a')
case has completed.
Update
You can review the source code of both the useState
and useReducer
hooks and see that they will process updates in the same synchronous, sequential manner.
Dispatch
type Dispatch<A> = A => void;
Here you can see that dispatch
is a synchronous function.
useState
export function useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
useReducer
export function useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
const dispatcher = resolveDispatcher();
return dispatcher.useReducer(reducer, initialArg, init);
}
Both useState
and useReducer
use the same internal dispatch system (i.e. resolveDispatcher
). Since useReducer
can't handle asynchronous actions (i.e. like Redux-Thunks) there is no other option but to synchronously handle actions in the reducer before handling the next dispatched action.
Update 2
MODIFIED: And what about reducers and setStates combined? Is their
order of calls also kept? e.g., a setState, a reducer (which uses the
value of the state set in the setState).
The order will be maintained, but you won't be able to enqueue a state update and then dispatch an action based on that updated state. This is due to the way React state updates are asynchronously processed between render cycles. In other words, any setState(newState)
won't be accessible until the next render cycle.
Example:
const [countA, setCountA] = useState(0);
const [countB, dispatch] = useReducer(.....);
...
setCountA(c => c + 1); // enqueues countA + 1, next countA is 1
dispatch(setCountB(countA)); // dispatches with countA === 0
setCountA(c => c + 1); // enqueues countA + 1, next countA is 2
dispatch(setCountB(countA)); // dispatches with countA === 0