In this github redux example, a dispatch of the event ADD_TODO is used to add a task. During the debugging, I found out that adding a task causes both the reducers todos and visibilityFilter being called.
How can I call just the todos reducer and not visibilityFilter reducer when I add a task. Also the visibilityFilter reducer if I sent an event of type SET_VISIBILITY_FILTER.
The combineReducers utility intentionally calls all attached reducer functions for every action, and gives them a chance to respond. This is because the suggested Redux reducer structure is "reducer composition", where many mostly-independent reducer functions can be combined into one structure, and many reducer functions could potentially respond to a single action and update their own slice of state.
As mentioned in other answers, combineReducers calls every reducer whenever a dispatch is called. You can avoid the other values changing, by making the default case equal to the state parameter passed in, so essentially they are reassigned their current value.
For example:
const individualReducer = (state = "initialState", action) => {
switch(action.type)
{
case "ACTION_TYPE":
return action.payload;
default:
return state;
}
}
export default individualReducer;
Related
In the Redux Style Guide, it is strongly recommended to Put as Much Logic as Possible in Reducers:
Wherever possible, try to put as much of the logic for calculating a
new state into the appropriate reducer, rather than in the code that
prepares and dispatches the action (like a click handler).
What I'm not sure of is, if thunks are also considered to be "the code" of some sort. Besides, we've also been (mis?)using thunks to grab data from other slices of state.
Hypothetically simplified code snippet of such thunk:
const addX = x => (dispatch, getState) => {
const { data, view } = getState();
const { y } = view; // <-- here accessing data from `view` state.
const yy = doSomeLogicWith(y);
const z = doSomeMoreLogicWith(yy);
dispatch({ type: 'data/xAdded', payload: { x, z } });
};
Is this actually considered to be an anti-pattern in Redux? If so, what are the cons of doing this?
Yes, a thunk would qualify as "the code that dispatches the action" for this case. So, what the rule is recommending here is that if possible, the action would just contain y, and the function calls to doSomeLogicWith(y) and doSomeMoreLogicWith(yy) would ideally exist within the reducer instead.
Having said that, it's totally fine for a thunk to extract pieces of data from the state and include that in the action, and it's not wrong for a thunk to do some pre-processing of data before dispatching the action.
The style guide rule is just saying that, given a choice between running a particular piece of logic in a reducer or outside a reducer, prefer to do it in the reducer if at all possible.
I have a project in which I have applied the redux-thunk middleware to my redux store. Now I have several thunks in my code and this are also being dispatched, but they return ThunkAction<Promise<void>, void, void, AnyAction> so I assume these are async.
In other places of my code I’m directly calling the dispatch method on the store. Does this actions also become asynchronous as an effect of applying middleware or do they remain synchronous?
For example If i do:
store.dispatch(someAction);
Would that still be synchronous?
Thanks in advance.
Yes.
Dispatching is 100% synchronous by default.
If you add middleware, a middleware may alter, delay, or stop an action from progressing through the dispatch pipeline.
However, in this case, the redux-thunk middleware does not do anything asynchronous. It simply checks to see if the "action" is actually a function, and if so, executes it immediately:
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
If the thunk middleware is applied, store.dispatch(someThunkFunction) will return whatever the thunk function returns. So, if you return a promise from the thunk, that promise will be returned from store.dispatch().
For the TS types, the first generic argument should indicate the expected return type of the thunk function. However, you would likely actually have to specify that yourself - it shouldn't be appearing randomly.
If I have code like this,
dispatch({
type: actionTypes.ABCD,
...newInfo,
});
const state = getState();
Can I rely 100% on state being the latest state? Or is dispatch asynchronous and I have no guarantee on whether or not the state has updated?
Yes. A call to dispatch(), by itself, is 100% synchronous. By the time dispatch() returns, the root reducer has been run, the state value has been updated, and all subscribers have been notified. So yes, a call to getState() immediately after dispatch() will return the latest value.
The caveat to that is that any middleware can intercept, delay, or modify a dispatched action. So, the final answer is dependent on what middleware you have installed and how they are configured.
getState() does retrieve the latest value, but if you are not mutating your state (which you shouldn't be, for example with the Immer library), you have to be careful if you set it to a 'state' variable inside your action creator.
For example:
let state = getState();
dispatch({
type: actionTypes.someStateChange,
newInfo1: newInfo
});
dispatch({
type: actionTypes.someOtherAction,
infoDependentOnNewInfo1: state.newInfo1.property
});
This will not behave as expected because state as define above will have the old state, since the first dispatch has created a new immutable state that is not referenced by the 'state' variable.
I have 2 reducers that are combined in a Root Reducer, and used in a store.
First reducer 'AllTracksReducer" is supposed to return an object and the second 'FavoritesReducer' an array.
When I create a container component and a mapStateToProps method in connect, for some reason the returned state of the store is an object with 2 reducer objects which hold data, and not just an object containing correposding data, as expected.
function mapStateToProps(state) {
debugger:
console.dir(state)
//state shows as an object with 2 properties, AllTracksReducer and FavoritesReducer.
return {
data: state.AllTracksReducer.data,
isLoading: state.AllTracksReducer.isLoading
}
}
export default connect(mapStateToProps)(AllTracksContainer);
so, in mapStateToProps, to get to the right state property, i have to say
state.AllTracksReducer.data... But I was expecting the data to be available directly on the state object?
Yep, this is a common semi-mistake. It's because you're using likely using ES6 object literal shorthand syntax to create the object you pass to combineReducers, so the names of the imported variables are also being used to define the state slice names.
This issue is explained in the Redux docs, at Structuring Reducers - Using combineReducers.
Create some selectors that receive the whole state (or the reducer-specific state) and use it in your mapStateToProps function. Indeed the name you define when you combineReducers will be the topmost state keys, so your selectors should take that into account:
const getTracks = (state) => state.allTracks.data
const isLoading = state => state.allTracks.isLoading
This assumes you combine your reducers with allTracks as they key like here:
combineReducers({
allTracks: allTracksReducer
})
And then you can use those selectors in your mapper, like
const mapStateToProps = state => ({
isLoading: isLoading(state),
tracks: getTracks(state)
})
There's a delicate link between your combineReducers call and your selectors. If you change the state key name you'll have to update your selectors accordingly.
It helps me to think of action creators as "setters" and selectors as "getters", with the reducer function being simply the persistence part. You call your setters (dispatching action creators) when you want to modify your state, and use your selectors as shown to get the current state and pass it as props to your components.
Well, that's how it supposed to work. When you're using combineReducers, you're literally mapping the name of a reducer to the reducer function.
If it bothers you, I would suggest a little syntactical magic if you're using es2016 (though it seems you're not) like so:
function mapStateToProps(state) {
const { data, isLoading } = state.allTracksReducer;
return {
data: data,
isLoading: isLoading
}
}
export default connect(mapStateToProps)(AllTracksContainer);
Remember, state is the one source of truth that possesses all your reducers.
In this example I'm using an action named ADD_TODO
import { createStore, combineReducers } from 'redux';
function todos(state, action) {
state = state || [];
switch (action.type) {
case 'ADD_TODO':
return state.concat([ action.text ]);
default:
return state;
}
}
function counter(state, action){
state = state || 0;
switch (action.type){
case 'INCREMENT':
return state+1;
case 'DECREMENT':
return state-1;
case 'ADD_TODO':
return state+100;
default:
return state;
}
}
var app = combineReducers({
todos: todos,
counter: counter
});
var store = createStore(app);
store.dispatch({ type: 'ADD_TODO': text: 'buy eggs' });
This cause both the "todos" and "counter" reducers to trigger.
Should I make all reducers have unique actions unless I actually intended it?
How can we implement this with multiple reducers that almost do the same thing? Multiple counters for example can have "INCREMENT" and a "DECREMENT" actions.
Should name spacing actions solve it?
eg: "POINT_INCREMENT", "POINT_DECREMENT".
There's nothing inherently wrong with having different reducers respond to the same action -- for example, if you refresh the entire state at once. But yeah, if you have two counters that correspond to different things, you probably want to come up with a naming scheme to differentiate. But I would think the action names probably should have some noun to indicate what they apply to.
This cause both the "todos" and "counter" reducers to trigger. Should I make all reducers have unique actions unless I actually intended it?
Yes, probably they should have different unique actions.
From your example it becomes not really clear what you actually intend.
Should the counter count the amount of todo's ?
In that case it can actually be sensible that a "ADD_ITEM" action would both update the counter and also add a todo item. In that case please refer to the answer of acjay
How can we implement this with multiple reducers that almost do the same thing? Multiple counters for example can have "INCREMENT" and a "DECREMENT" actions.
When displaying a list of counters in the same app, each counter can be assigned a unique identifier (id).
An action should pass along the id of the counter.
export const toggleTodo = id => ({
type: 'INCREMENT',
id
})
The reducer should then check by id which counter to update.
See this example of a todo list in the official redux docs.
https://redux.js.org/basics/example
Redux actions are in a way globals. There are different strategies to workaround this problem on a larger scale.
https://kickstarter.engineering/namespacing-actions-for-redux-d9b55a88b1b1