Detecting state changes with Redux-Saga
11 Oct 2017
Redux-Saga allows us to reactively respond to Redux actions swiftly:
function* someSaga() {
yield take('ACTION_NAME');
// will be executed after ACTION_NAME was dispatched
}
Sometimes, though, rather than relying on (or even caring for) the actions themselves, we want to observe the changes in some part of the Redux state.
Say we wanted to ensure we only run some actions:
- if there is a current user, proceed;
- if there is no current user, wait for it, and only then proceed;
Who the current user is, is stored somewhere in the state and can be changed by at least three actions: LOG_IN
, SIGN_UP
, and REHYDRATE_SESSION
.
But to implement the requirement, simply listening for the actions is not enough. We'd also have to check the state — in case the user is already logged in:
function* someSaga() {
if (!(yield select(getCurrentUser))) {
yield take('LOG_IN', 'SIGN_UP', 'REHYDRATE_SESSION');
}
// the user is logged in by now
}
Isn't it redundant to specify the exact actions if we are checking a part of the state anyway?
Instead, would this be a better API?
function* someSaga() {
yield call(waitFor, state => getCurrentUser(state) != null);
// the user is logged in by now
}
Here's how we could implement this waitFor
function:
- Run the selector first. If it returns true, exit the function.
- Otherwise take every action and run the selector. Run it until it returns true. Then exit the function.
In code, it would be:
function* waitFor(selector) {
if (yield select(selector)) return; // (1)
while (true) {
yield take('*'); // (1a)
if (yield select(selector)) return; // (1b)
}
}
(Passing *
as the action type in 1b means take
will wait for any action.)
And now we have a simple but powerful utility for detecting state changes.