Reputation: 6544
Usually in Redux we listen for actions in middleware/reducers to perform operations. However, at times we're not interested in actions, but in store changes themselves. This is especially relevant when multiple actions can lead to the same store changes. How can I do so in an idiomatic way?
Consider for example a section of the store in which we have the current path of a SPA:
const store = {
url : '/'
};
Changing the url can happen in one of 3 ways: popState
, pushState
or replaceState
. A logical implementation would be to have separate actions for the 3, such that we can handle them separately in middleware. E.g.:
//actions
function popState(url) {
return { type : 'POP_STATE', url };
}
function pushState(url) {
return { type : 'PUSH_STATE', url };
}
function replaceState(url) {
return { type : 'REPLACE_STATE', url };
}
// middleware
store => next => action => {
switch(action.type) {
case 'PUSH_STATE' :
history.pushState(null, null, action.url);
break;
case 'REPLACE_STATE' :
history.replaceState(null, null, action.url);
break;
}
return next(action);
}
// reducer
function (state, action) {
switch(action.type) {
case 'PUSH_STATE':
case 'REPLACE_STATE':
case 'POP_STATE':
state.url = action.url; // (ignoring immutability to keep the example simple)
break;
}
return state;
}
Now I want to respond to url changes, but I don't care how the url changed. I see a few options, but I'm not sure if either of them follows the redux paradigms properly:
Use subscribe
This seems to be discouraged. From the docs:
It is a low-level API. Most likely, instead of using it directly, you'll use React (or other) bindings.
Listen to all actions in middleware/reducer
This only works properly in the simplest of cases. If the actions differ in structure, or if the full set of actions that change the state isn't known, then this won't work.
Dispatch a new action (e.g. STATE_CHANGED
) after reducing
You can't directly dispatch an action in a reducer. This means you either need to use a timeout (subject to race conditions), or you need to spread out the logic over multiple redux-thunk
style action creators.
Use middleware to dispatch a new action
This requires middleware to be aware of how all actions are reduced.
Do any of these solutions fall within the paradigms of redux? Did I miss any?
Meta note: I'm aware that this question could be interpreted as "primarily opinion based", but I disagree: I'm not looking for the best or most elegant solution, I'm trying to assert which solution(s) fit(s) the redux paradigm, and I believe there are definite, objective answers to that.
Upvotes: 0
Views: 215
Reputation: 11177
Good pattern middleware :
const logMiddleware => ({ getState, dispatch }) => next => action => {
console.log("Before reducers have run");
console.log(getState());
next(action);
console.log("After the reducers have run");
console.log(getState());
};
Upvotes: 0
Reputation: 67459
Using store.subscribe()
is a valid option if you need it. There's many existing libraries to help watch for changes in the store state if you need to do so outside of connected React components. Given that you're doing routing-related behavior, you may also be interested in some of the existing Redux-based routing libraries as well.
Upvotes: 1