How does combineReducers being provided with the right state slices - redux

I am very new to Redux.
I've been reading its' Reducers documentation, ran into their combineReducers function that I cannot seem to understand.
They note:
All combineReducers() does is generate a function that calls your reducers with the slices of state selected according to their keys, and combining their results into a single object again. It's not magic.
I do understand it returns a combined reducer just like it sounds, BUT how does it provide each reducer the specific relevant slices of state - what does according to their keys mean?
Diving into their It's not magic git issue helped me understand more about the use of combineReducers but I still fail to understand the according to their keys pars.
Any explanation would be highly appreciated, thank you.

A: If your state slice and reducer function has similar names - that reducer will be provided with that slice.
EXAMPLE:
const state = {
slice1 : {...},
slice2 : {...}
}
function slice1(state, action){...}
function slice2(state, action){...}
combineReducers({slice1, slice2}) //Reducers will be provided with appropriate slices.
B: If you provide keys to combineReducers function, these keys must match state slice keys.
EXAMPLE:
const state = {
slice1 : {...},
slice2 : {...}
}
function reducer1(state, action){...}
function reducer2(state, action){...}
combineReducers({slice1 : reducer1,
slice2 : reducer2}) //Reducers will be provided with appropriate slices.
C: Both approaches could be mixed:
EXAMPLE:
const state = {
slice1 : {...},
slice2 : {...}
}
function slice1(state, action){...}
function reducer2(state, action){...}
combineReducers({slice1,
slice2 : reducer2}) //Reducers will be provided with appropriate slices.

I think some confusion could arise if you are not really familiar with new ES6 syntax.
If you look at redux documentation you could see that they use ES6 object literal shorthand syntax to define the object shape
const rootReducer = combineReducers({
theDefaultReducer,
firstNamedReducer,
secondNamedReducer
});
this short hand actually could be translate in:
var rootReducer = combineReducers({
theDefaultReducer: theDefaultReducer,
firstNamedReducer: firstNamedReducer,
secondNamedReducer: secondNamedReducer
});
As you see the keys match the separate single reducers, in this way the combineReducers knows what to map.

Related

Redux Toolkit says this snippet(19 lines) is shorter code (vs. original 12) confusion

I am reading this page getting into react-redux https://redux.js.org/introduction/getting-started
I am very confused looking at the Basic Example which has 12 lines of code(excluding usage, imports, and comments)
Then I read this line on the "Redux Toolkit Example" which below the code states "Redux Toolkit allows us to write shorter logic that's easier to read, while still following the same Redux behavior and data flow." However, this example is 19 lines of code(excluding usage, imports, and comments)
The usage in both examples is 3 lines of code. Could someone explain this to me?
Perhaps when it scales, the redux toolkit example does save more code? Honestly, I find the Basic Example MUCH easier to read and maintain. NOTE: I am a complete newb which leads me to believe the basic example may be better as we ramp up and hire developers. This allows them to ramp up more quickly(but I am only a single data point amongst newbs)
thanks,
Dean
You're right about the lines of code in the example. Perhaps that simple counter example doesn't do justice to how much code Redux Toolkit can save because they aren't adding all the "bells and whistles" in their non-toolkit version.
This section is called "getting started with Redux" rather than "migrating to Redux Toolkit" so I suspect they don't want to overwhelm the user by introducing best practices like action creator functions which aren't strictly necessary. But you're not seeing the "write less code" benefit because most of the code that you can remove with the Toolkit is coming from things that weren't in the example in first place.
Action Creators
One of the main benefits of the createSlice function is that it automatically creates the action name constants and action creator functions to go along with each case in the reducer.
This example is just dispatching raw actions directly with string names store.dispatch({ type: 'counter/incremented' }). Most devs would not do this because of how fragile and inflexible it is.
An alternate version of the non-toolkit example, what you would see in most code, looks more like this:
// action name constants
const INCREMENTED = 'counter/incremented';
const DECREMENTED = 'counter/decremented';
// action creator functions
// usually most take some arguments
const incremented = () => ({
type: INCREMENTED,
})
const decremented = () => ({
type: DECREMENTED,
})
// reducer function
function counterReducer(state = { value: 0 }, action) {
switch (action.type) {
case INCREMENTED:
return { value: state.value + 1 }
case DECREMENTED:
return { value: state.value - 1 }
default:
return state
}
}
If you want to include typescript types it gets even worse.
Immutability
The reducer itself could get really lengthy if you are trying to do immutable updates on deeply nested data.
Here's an example copied from those docs on how to safely update the property state.first.second[someId].fourth
Without Toolkit
function updateVeryNestedField(state, action) {
return {
...state,
first: {
...state.first,
second: {
...state.first.second,
[action.someId]: {
...state.first.second[action.someId],
fourth: action.someValue
}
}
}
}
}
With Toolkit:
const reducer = createReducer(initialState, {
UPDATE_ITEM: (state, action) => {
state.first.second[action.someId].fourth = action.someValue
}
})
configureStore
The Toolkit configureStore actually does save a step vs the Redux createStore function when you are combining more than one reducer. But again this example fails to show it. Instead the Toolkit version is longer because we set a reducer property rather than just passing the reducer.
A typical Redux app uses the combineReducers utility to combine multiple reducers as properties on an object:
import {createStore, combineReducers} from "redux";
const rootReducer = combineReducers({
counter: counterReducer,
other: otherReducer
});
const vanillaStore = createStore(rootReducer);
With the Toolkit you can just pass your reducers map directly without calling combineReducers.
import {configureStore} from "#reduxjs/toolkit";
const toolkitStore = configureStore({
reducer: {
counter: counterReducer,
other: otherReducer
}
});
Which is roughly the same amount of code. But it also includes some default middleware which would be extra lines in the non-toolkit example.

Redux. Is .map() makes reducer non-pure function?

I just want to understand is the usage of .map() inside reducers makes them non-pure? I clearly understand that pure functions is the functions that always return predictable (lets say dirty - "the same") result. But I think that usage of .map() inside reducer makes result non-predictable, because it lets him go ahead by one of two ways in ternary operation, that is a non-puse function way. So, just look on my reducer code and say is I'm wrong or not?
Thank you! )
// .. reducer
[SELECT_CDS]: (state, action) => ({
...state,
crimesByType: state.crim.map(
(cri, i) =>
i === 0
? {
...cri,
additionalInfo: {
...cri.addition,
CDsLeft: true
}
}
: crime
)
})
A reducer should be pure function, meaning that if the reducer is called twice with the same input, the output should also be the same.
In your case, the reducer is pure, because even though your map() and ternary operator give different results for different elements of the array, the final result will always be the same if the original array and action are the same.

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.

Is it possible to create a selector that accesses multiple branches of the state tree?

I am working on a Redux project with a bunch of reducers that are combined
const rootReducer = combineReducers({
reducer1,
reducer2,
...
});
The reducers each update a branch of the store
sampleStore = {
reducer1: {a1: 7, b1: 5, c1: 6, ...},
reducer2: {a2: 3, b2: 2, c2: 9, ...},
reducer3: {a3: 1, b3: 4, c3: 2, ...},
...
}
Now I am trying to add logic that requires access to both branches of the store without restructuring the entire project. For instance, I want a reducer with the ability to perform: set c3 = f(a1, a2).
At the moment I am trying to use reselect but I am getting lost. In reducer1/selector.js I have
export const derivedVariableSelector = createSelector(
a1Selector,
a2Selector,
(a1, a2) => a1 + a2
);
And I am trying to create a reducer
function setC3(state) {
return Object.assign({}, state, {
c3: state.c3 + derivedVariableSelector(state)
});
}
However, when setC3 is called, derivedVariableSelector will never receive enough of the store to do its job. Either it will receive the branch containing a1 or the branch containing a2, but I don't know of a way to supply both. Is this possible?
The short answer is "No", so long as you're using combineReducers to combine all your reducers.
The longer answer is taken from the redux documentation:
The combineReducers utility included with Redux is very useful, but is
deliberately limited to handle a single common use case: updating a
state tree that is a plain Javascript object, by delegating the work
of updating each slice of state to a specific slice reducer. It does
not handle other use cases, such as a state tree made up of
Immutable.js Maps, trying to pass other portions of the state tree as
an additional argument to a slice reducer, or performing "ordering" of
slice reducer calls. It also does not care how a given slice reducer
does its work.
The common question, then, is "How can I use combineReducers to handle
these other use cases?". The answer to that is simply: "you don't -
you probably need to use something else". Once you go past the core
use case for combineReducers, it's time to use more "custom" reducer
logic, whether it be specific logic for a one-off use case, or a
reusable function that could be widely shared.
On the same page of the docs are some suggestions for how the create reducers that share more of the state but it's going to be a bit more manual than comibineReducers is.
I haven't tried anything like this myself before, but following examples in the docs, something like this might work for you:
import { combineReducers } from 'redux'
import reduceReducers from 'reduce-reducers'
const reducer1 = (state = {}, action) => {
switch (action.type) {
// handle actions
default:
return state
}
}
const reducer2 = (state = {}, action) => {
switch (action.type) {
// handle actions
default:
return state
}
}
const reducer3 = (state) => {
return {
...state,
// use the root state for the reducerC value
reducerC: makeValue(state.reducer1, state.reducer2)
}
}
const rootReducer = reduceReducers(combineReducers({reducer1, reducer2}), reducer3)
This uses the library reduce-reducers to combine reducers in a different way.

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.

Resources