How to share state between 2 combine reducers? - redux

I have 2 reducers that I use and combine them. In the first reducer, I have something that gets all the initial data (which is also relevant for the second reducer).
How do I use the data in the state that I initialize/set from the first reducer to the second one?
function reducer1(state = initialState, action = '') {
switch (action.type) {
case constants.INITIAL_DATA:
returnstate.set('data', fromJS(document.data));
....
Then I combine both of those reducers and I want to access "data" from both of them (or pass the data as initialState to the second reducer).

A reducer should return a plain object and have no side effects. If you want the same state available in 2 different action.type cases, then they either need to be in the same function, or a parent function needs to pass the state to different reducer functions, like so:
function parentReducer(state = {}, action = '') {
switch (action.type) {
case CASE_ONE:
return childReducer1(state, action)
case CASE_TWO:
return childReducer2(state, action)
default:
return state
}
}
But this brings up an important design point: each (top-level) reducer represents a distinct branch of the Redux state tree, and you can probably always design your data store in a way that different parts of the tree don't need to be shared. In Redux (check out the DevTools), you have a single object, and the top-level keys of this object are the names of your top-level reducer functions.
Basically, if you perceive a need to set a different state in a reducer, so other reducers can use that, it evidence of a need to rethink the store's design.

Related

Can I pass always the full state to reducers?

Is there any inconvenient at all if I design my reducers to, instead of reading only the partial state, had access to the full state tree?
So instead of writing this:
function reducer(state = {}, action) {
return {
a: doSomethingWithA(state.a, action),
b: processB(state.b, action),
c: c(state.c, action)
}
}
I destructure state inside doSomethingWithA, c or processB reducers, separately:
function reducer(state = {}, action) {
return {
a: doSomethingWithA(state, action), // calc next state based on a
b: processB(state, action), // calc next state based on b
c: c(state, action) // calc next state based on a, b and c
}
}
Would I'd be using more RAM? Is there any performance inconvenient? I understand that in javascript, a reference is always passed as parameter, that's why we should return a new object if we want to update the state or use Immutable.JS to enforce immutability, so... again, would it be of any inconvenient at all?
No, there's nothing wrong with that. Part of the reason for writing update logic as individual functions instead of separate Flux "stores" is that it gives you explicit control over chains of dependencies. If the logic for updating state.b depends on having state.a updated first, you can do that.
You may want to read through the Structuring Reducers section in the Redux docs, particularly the Beyond combineReducers topic. It discusses other various reducer structures besides the typical combineReducers approach. I also give some examples of this kind of structure in my blog post Practical Redux, Part 7: Form Change Handling, Data Editing, and Feature Reducers.

Popup and Redux Reducers

I'd like to know how to handle specific use case with redux reducer. To give an example, say I have a form with a DataGrid/Table. On Edit button click from a row, I want to open the popup with seleted row-data. After editing the data, On Popup-Submit button click, I want to update the Table/DataGrid (i.e. DataGrid will now should have the edited values).
I've written two separate Components
1. MainPage.js and its corresponding reducer MainPageReducer (Employee List)
2. PopupPage.js and its corresponding reducer PopupPageReducer (Selected Employee)
How these two reducers share the state?
You may need to read this first
http://redux.js.org/docs/basics/UsageWithReact.html
The main concept is that through the connect function, you would simply map needed properties of your state to the properties of your component i.e MapStateToProps. So in your case, imagine that your state, for contrived purposes, is structed like so:
{employees: {employees: {1: {id: 1, name: 'Foo'}}, editedEmployeeId: 1}
You could map the array of employees to an employees property for your EmployeeList component whilst also mapping a dispatch function, named editEmployee(id) to a click function on each row in the table.
You could map [the employee with the associated editedEmployeeId] to the individual employee in your employees array for your popup component
It may be efficient to just use one reducer instead of two.
Specifically, if you're making an update to an individual employee then you would call an EDIT_EMPLOYEE action and then a SAVE_EMPLOYEE action on save. After the SAVE_EMPLOYEE action, then, I assume, you'd call a post method, and then react-redux would re-render your entire list.
It could look like this:
function employees(state = {editedEmployeeId: undefined, employees = []}, action) {
switch(action.type) {
case EDIT_EMPLOYEE:
return Object.assign({}, state, {editedEmployee: action.employee_id})
case SAVE_EMPLOYEE:
return Object.assign({}, state, {employees: action.employees});
default:
return state;
}
}
There are great holes in my answer because the question you're asking might be too broad; I'm presuming you don't fully understand how the connect, subscribe, and dispatch functions work.
Like one of the comments said, reducers don't share state. They simply take the previous version of your state and return another version of it.
Anyways, hope this helps. Read the redux docs!

React Redux - state returned in mapStateToProps has reducer names as properties?

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.

Redux with Immutable JS

When using immutablejs with Redux, we will get a regular javascript object back from combineReducers, meaning it won't be an immutable data structure even if everything within it is. Doesn't this mean that using immutablejs will be in vain since a whole new state object will be created on every action anyhow?
Example:
const firstReducer = (state = Immutable.Map({greeting : 'Hey!'})) => {
return state
}
const secondReducer = (state = Immutable.Map({foo : 'bar'})) => {
return state
}
const rootReducer = combineReducers({
firstReducer, secondReducer
})
a whole new state object will be created on every action
Yes but the slices of that state that are assigned by combineReducers are not recreated. It's sort of analogous to doing this:
const person = { name: 'Rob' };
const prevState = { person };
const nextState = { person };
I created a new state object (nextState), but its person key is still set to the same exact object as was prevState's person key. There is only one instance of the string 'Rob' in memory.
The problem is when I mutate the person object, I'm changing it for multiple states:
const person = { name: 'Rob' };
const prevState = { person };
person.name = 'Dan'; // mutation
const nextState = { person };
console.log(prevState.person.name); // 'Dan'
Coming back to Redux, once all reducers have been called for the first time, they will have initialized their slices of the application's state, and your application's entire overall state would basically be equal to this:
{
firstReducer: Immutable.Map({greeting : 'Hey!'}),
secondReducer: Immutable.Map({foo : 'bar'}),
}
Note that this is a normal object. It has properties that hold Immutable objects.
When an action is dispatched and goes through each reducer, the way you've got it, the reducer simply returns the existing Immutable object back again, it doesn't create a new one. Then the new state is set to an object with a property firstReducer simply pointing back to the same Immutable object that the previous state was pointing to.
Now, what if we didn't use Immutable for firstReducer:
const firstReducer = (state = {greeting : 'Hey!'}) => {
return state
}
Same idea, that object that is used as the default for state when the reducer is first called is just passed from the previous state to the next state. There is only ever one object in memory that has the key greeting and value Hey!. There are many state objects, but they simply have a key firstReducer that points to the same object.
This is why we need to make sure we don't accidentally mutate it, but rather replace it when we change anything about it. You can accomplish this without Immutable of course by just being careful, but using Immutable makes it more foolproof. Without Immutable, it's possible to screw up and do this:
const firstReducer = (state = {greeting : 'Hey!'}, action) => {
switch (action.type) {
case 'CAPITALIZE_GREETING': {
const capitalized = state.greeting.toUpperCase();
state.greeting = capitalized; // BAD!!!
return state;
}
default: {
return state;
}
}
}
The proper way would be to create a new state slice:
const firstReducer = (state = {greeting : 'Hey!'}, action) => {
switch (action.type) {
case 'CAPITALIZE_GREETING': {
const capitalized = state.greeting.toUpperCase();
const nextState = Object.assign({}, state, {
greeting: capitalized,
};
return nextState;
}
default: {
return state;
}
}
}
The other benefit Immutable gives us is that if our reducer's slice of the state happened to have a lot of other data besides just greeting, Immutable can potentially do some optimizations under the hood so that it doesn't have to recreate every piece of data if all we did was change a single value, and yet still ensure immutability at the same time. That's useful because it can help cut down on the amount of stuff being put in memory every time an action is dispatched.
combineReducers() that comes with Redux indeed gives you a plain root object.
This isn’t a problem per se—you can mix plain objects with Immutable objects as long as you don’t mutate them. So you can live with this just fine.
You may also use an alternative combineReducers() that returns an Immutable Map instead. This is completely up to you and doesn’t make any big difference except that it lets you use Immutable everywhere.
Don’t forget that combineReducers() is actually easy to implement on your own.
Doesn't this mean that using immutablejs will be in vain since a whole new state object will be created on every action anyhow?
I’m not sure what you mean by “in vain”. Immutable doesn’t magically prevent objects from being created. When you set() in an Immutable Map, you still create a new object, just (in some cases) more efficiently. This doesn’t make a difference for the case when you have two keys in it anyway.
So not using Immutable here doesn’t really have any downsides besides your app being slightly less consistent in its choice of data structures.

Should Actions in Redux always be unique?

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

Resources