I am new to Redux. I am wondering about a best approach to using an isFetching flag for multiple different reducers.
I am following something similar to this example:
http://redux.js.org/docs/advanced/AsyncActions.html
Except I have 2 separate API calls. When I send out API call I dispatch an action that sets isFetching : true.
return Object.assign({}, state, {isFetching: true});
When I get response I set isFetching:false.
return Object.assign({}, state, {data: myData}, {isFetching: false});
The issue is isFetching is global in my state, so if I am correct they can overwrite eachother. What is the best way to use a generic 'AJAX_FETCHING' or 'AJAX_DONE' action that multiple reducers can use independent of each other?
Related
I have a case where I get a data returned from API and I need to arrange this data to each reducer. There are two ways of designing it:
Сase when multiple actions are being dispatched to each particular reducer:
export function actionFetchData() {
const data = getDataFromApi()
dispatch({ type: firstReducer.someAction, payload: { data: data.someProp })
dispatch({ type: secondReducer.someAction, payload: { data: data.anotherProp })
// .. and so on
}
Case when only one generic action is being dispatched and only specific reducers should handle this one:
export function actionFetchData() {
const data = getDataFromApi()
dispatch({ type: someGenericAction, payload: { data })
}
I know that the first option is more controllable, for example, what if I need to pass some another data for an action and handle it in a specific reducer.
But the reduxjs docs says: put as much reducer logic as possible in reducers.
That's confusing. What option is more appropriate when designing store?
Per the Redux Style Guide, we do encourage "keeping as much logic as possible in reducers", as well as modeling actions as "events", not "setters".
In this case, there's no need to dispatch two separate actions. Dispatch one action containing all the data from this API response, and let each reducer handle that as appropriate to update its own piece of state.
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 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 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;
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