Should we compare new state with old state in the reducer - javascript

I am using spread operator in the reducer function, so what is happening when i am dispatching the action then it is creating new state (obj) even old state is same as like new state so it is re-rendering react component.
Is it good approach to compare new state with old state as object could be complex ? Deep comparison ?
const initialState = {};
export const reducer = (state = initialState, action: any = {}): any => {
if (action.type === ActionTypes.RC) {
return {
...state,
...action.config,
};
}
return state;
};
Or redux does it out of the box for you in mapStateToProps ?

There a few things worth noting here.
React Redux uses strict equality (reference equality) as the default method for comparisons. The immutable nature of Redux state means that any change to a deeply-nested property requires that all of its ancestor must be new object references.
If you update a.b.c then a and a.b must be new objects. Let's say that you have a sibling property tree like a.d.e. When you update a.b.c it causes a to be a new object reference, but a.d and a.d.e are the same. If you have a component that only needs to know about a.d then it should not need to re-render in response to your a.b.c change.
This is why it's important that each component should only select the minimal information that it needs from the state. You should connect more individual components rather than selecting a lot of information from state and passing it down through props. You can use multiple useSelector hooks in the same component to select small, granular pieces of state.
You do have the ability to customize the equality comparison by passing a comparison function as the second argument to the useSelector hook.
If you have a component that subscribes to the piece of state that's controlled by this reducer then you can use the shallowEqual function which is included in the react-redux package. I'm not sure if the reducer in your question is your root reducer or something that you are passing to combineReducers. I'm assuming it's just one piece.
Let's say it's for a property settings and you have some updateSettings action that merges the new settings with the existing ones. Then when you have a component that needs to access the settings, you would do this:
import { shallowEqual, useSelector } from 'react-redux'
const settings = useSelector(state => state.settings, shallowEqual);
This would mean that the component will only re-render if some of the settings have actually changed, even if state.settings is a new object. It is doing a shallow comparison between the current value of state.settings and the previous one.
It is possible to use a deep equals comparison like lodash's isEqual but I'm not seeing why you would need it as this reducer is making a shallow copy. If you need to do that expensive check anywhere then it seems more logical to do it before you dispatch the action in the component or before you return a new object from the reducer rather than on every call of the useSelector hook.
You asked about whether you should check for changes in the reducer before returning a new state object. That's a good idea and it's actually what a combined reducer created by combineReducers does, though it only checks for shallow equality. See lines 177-201 of the source code. If all of the individual property reducers returned the same object as before then it will return the previously combined state in order to maintain object references.

personal opinion:
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]);// <= re-render state diff here
The page should be re-render or not is decided by react, or says: will be better.
There's dom-diff algorithm in react.
You dispatch action from multiple pages, these pages pick their own state from newState, and diff themselves, trigger re-render or not.
So i think you don't have to run another diff algorithm in reducer.
consider case:
You have PageA & PageAB, state { a:1, b:1 }
PageA re-render by a. PageAB re-render by a,b
Now your state become {a:1, b:2}. How could you make PageA not re-render in reducer? you will diff it in PageA.

Related

Is there a performance difference between using useSelector once or multiple times?

I'm basically wondering if there is a difference in performance, or any other pros and cons, between the following 2 code snippets. Are there any objective reasons to use one over the other, or is it just personal preference?
const prop1 = useSelector((state) => state.appModel.prop1);
const prop2 = useSelector((state) => state.appModel.prop2);
const prop3 = useSelector((state) => state.appModel.prop3);
const prop4 = useSelector((state) => state.appModel.prop4);
const {
prop1,
prop2,
prop3,
prop4,
} = useSelector((state) => ({
prop1: state.appModel.prop1,
prop2: state.appModel.prop2,
prop3: state.appModel.prop3,
prop4: state.appModel.prop4,
}));
The second option instinctively feels like it might be more performant, because it only uses useSelector once, but then I wonder if one property changing may cause more re-renders because they're all grouped together.
UPDATE:
Also wondering if this even shorter version has any pros and cons?
const { prop1, prop2, prop3, prop4 } = useSelector((state) => state.appModel);
Apologies if this is a duplicate, I tried searching but wasn't entirely sure of the terminology to search for, and couldn't see any matching examples.
The individual one is much better performance wise. Your second approach creates a new object every time the selector is called, so when useSelector compares the before and after it always looks like it has changed. As a result, your component is forced to render on every change to the redux store, even if nothing happened to prop1-prop4.
If you want to do #2, you need to make a memoized selector, so that it returns the same object if none of the individual values have changed. You can read more about memoized selectors here: https://redux.js.org/usage/deriving-data-selectors#optimizing-selectors-with-memoization
(While it's good to learn about memoized selectors, for your case i would recommend sticking with approach #1)
Also wondering if this even shorter version has any pros and cons?
const { prop1, prop2, prop3, prop4 } = useSelector((state) => state.appModel);
That's better than #2, because now it will only need to rerender when appModel changes. If the only things in appModel are prop1-prop4, then this will be basically the same as #1. If there are extra properties in appModel that you don't care about, then this may result in a few extra renders when appModel changes due to those other properties.
If prioritizing performance efficiency from top to bottom, I think it would be like this:
const prop1 = useSelector((state) => state.appModel.prop1);
...
const {
prop1,
...
} = useSelector((state) => ({
prop1: state.appModel.prop1,
...
})
const { prop1,... } = useSelector((state) => state.appModel);
Always try to return primitive values, such as string, number, boolean, etc. when it is possible from callback of useSelector (or use memoizing techniques, see below). Because when you return an object value (object, array, array-like object, etc.) from callback function of useSelector, it causes a new re-rendering of that component, no matter whether any state was changed in your redux tree related to those states that you returned inside of every time new object or not. So, in cases 2 and 3 you are returning a value of object type (assuming prop1, ... is not object type value), therefore your component's props changes always and it re-renders it self by nature of React, even if rendering happens in the parent component of that component.
So, you need somehow memoize your object values to achieve better app performance, which helps to avoid unnecessary re-renderings. So, to do it you can pass shallowEqual function as a second paratmeter to useSelector from react-redux library. Of course, you can write your custom function to compare as well.
import { shallowEqual, useSelector } from 'react-redux'
const selectedData = useSelector(selectorReturningObject, shallowEqual)
Regarding the your use case, you can read more right below (including other memoize options to reference to the same object value, when component re-calls useSelector):
You may call useSelector() multiple times within a single function
component. Each call to useSelector() creates an individual
subscription to the Redux store. Because of the React update batching
behavior used in React Redux v7, a dispatched action that causes
multiple useSelector()s in the same component to return new values
should only result in a single re-render.
With useSelector(), returning a new object every time will always
force a re-render by default. If you want to retrieve multiple values
from the store, you can:
Call useSelector() multiple times, with each call returning a single
field value
Use Reselect or a similar library to create a memoized
selector that returns multiple values in one object, but only returns
a new object when one of the values has changed.
Use the shallowEqual
function from React-Redux as the equalityFn argument to useSelector()
Equality Comparisons and Updates in React-Redux

pass a useReducer state to another useReducer

I am trying to create a search for a simple todo-app, instead of trying to manage the filtered states throught the reducer that handles the todos, I was told to set another useReducer instead and then manage the filters throught that reducer, tho, on Redux (which is my reference based of) they use mapStateToProps.
The problem is that when I pass my state that comes from the todos useReducer down to my filter reducer state, it wont update as I change the reducer. And it is expected. My initial value is my todo's state (whose an empty array) and it will always be even when new todos are added to the list.
well, whats the ideal aproach in this case?
code:
Shaping your state is up to you -- shape it in a way that works best. Both Redux and React are unopinionated about that. If it makes sense to put those slices of state together, do that. With useReducer, think about storing the entire state for a component in it vs breaking it up. I look at useReducer as the alternative for a bunch of useState statements.
In the case of Redux, if a filter in a reducer needs to filter data in another reducer, _do that in the component, e.g.,
const filterdData = rawDataState.filter(x => x.someProperty === filterState)

Bind react component to part of redux state

I have redux store that looks something like this:
{
user: {},
alerts: [],
reports: [],
sourses: []
}
For each one of this parts of state i have a bunch of React Components wrapped in a container wich connected via react-redux. And has mapStateToProps like this
(state) => {alerts: state.alerts}
(state, ownProps) => {alert: _.filter(state, {id: ownProps.curId})}
Problem that when i for example launch some action for Alerts like CREATE_ALERT or EDIT_ALERT and redux state updated, ALL REACT COMPONENTS WILL RESPOND TO THIS CHANGE even ones that works with different parts like sources or reports.
My question: how to "bind" certain components to certain parts of a tree. So each container component WILL UPDATE ONLY WHEN APROPRIATE PART OF REDUX STATE UPDATED and ignore other changes.
Expected behavior
Dispatch CREATE_ALERT -> Alert reducer -> Redux store update -> ONLY Alert container component re-rendering.
When you are changing state in redux the whole state becomes just a new object.
Then your component is given by this new object (new reference) and re-renderes itself.
To fix this behaviour you need to add some logic to compare if your component got props with different value (not reference).
The easiest and fastest way is to use React.PureComponent. You can also override shouldComponentUpdate function and handle changes by yourself. But note that PureComponent works only with primitives (it does a shallow compare).
Check also Immutable.js which helps you with intelligent way of changing references of props.
if you use connect method, then pass only selected redux state to the component, this will prevent rendering of other components
example:
User Component:
const mapStateToProps = state =>({
users: state.users
});
export default connect(mapStateToProps)(User)
Alert Component:
const mapStateToProps = state =>({
alerts: state.alerts
});
export default connect(mapStateToProps)(Alert)
Check this out: Avoid Reconciliation
There explains what Neciu says.
Container components created with connect will always receive notifications of all updates to the store.
The responsibility for consuming these updates falls on the receiving connect component. It should contain the logic to extract the data relevant to it.

why does redux need to make a copy of the data each time it changes?

From what I've read, redux uses Object.assign or the spread operator to make a shallow copy of the data, but how does that make sense? I thought the whole point of it was to make a deep copy of the data that is dispatched so that if the data is changed outside the store, it won't change what's in the store also. if it were a shallow copy, then the data would be linked, which causes issues with the data changing what's in the store even without a dispatch, correct?
In the example below, if action.data were only shallowly copied, then if that data were changed from wherever it came from, it would change what's in the store, even without the dispatch right?
const activePokemon = (state = {}, action) => {
switch (action.type) {
case 'ADD_ACTIVE_POKEMON':
return {
...state,
...action.data
}
default:
return state
}
}
If you are following the Three Principles of redux, then you won't worry about changes outside the store.
The only way to change the state is to emit an action, an object describing what happened.
If the changes outside the store are intended to modify the state, dispatch should be used instead. In addition to update of the state, dispatch also notifies every subscriber of the store about the changes.
And once you 'linked' some data to the state, the data should not be changed, since it is a part of the state, and..
State is read-only
Edit: about making a copy
In the documentation of reducers(read it again for details!), redux only requires our reducers to stay pure. If the new state is different, the reducer must create new object, and making a copy is a way to describe the unchanged part.
We are not always making a copy to describe the new state. return {} can be used to clear all state properties, when there are only a few properties to keep, we can specify unchanged properties instead of copying:
return {
unchangedProp0: state.unchangedProp0,
unchangedProp1: state.unchangedProp1,
...newData
}

redux state selectors in top level reducer

After watching the new egghead course by Dan Abramov, I have question regarding the selectors that was mentioned.
The purpose of the selectors is to hide the details of the state tree from the components, so that it is easy to manage code later if tree changes.
If I understand it correctly, that means, the selectors called inside mapStateToProps should only be the ones that live in the top-level reducer. Because the state that is passed to mapStateToProps is the whole application state tree. If this is true, as the application grows, I can imagine it would become very difficult to manage the top level selectors.
Have I miss understood the concept here? or is this a valid concern?
Edit: trying to make my question clearer.
Say my whole state start with
{ byIds, listByFilter } and I have
export const getIsFetching = (state, filter) =>
fromList.getIsFetching(state.listByFilter[filter]);
in my top level reducer reducers/index.js, and components would simply use getIsFetching passing the whole state to is, which is totally fine because it is the top level.
However, later on, I decided my whole app is going to contain a todo app and an counter app. So it make sense to put the current top level reducers into reducers/todo.js, and create a new top level reducers reducers/index.js like this:
combineReducers({
todo: todoReducer,
counter: counterReducer
})
at the point my state would be like
{
todo: {
byIds,
listByFilter
},
counter: {
// counter stuff
}
}
components can no longer use the getIsFetching from reducers/todo.js, because the state in getIsFetching is now actually dealing with state.todo. So i have to in the top level reducer reducers/index.js export another selector like this:
export const getIsFetching = (state, filter) =>
fromTodo.getIsFetching(state.todo);
only at this point, the component is able to use getIsFetching without worring about the state shape.
However, this raises my concern which is all the selectors directly used by components must live in the top-level reducer.
Update 2: essentially we are exporting selectors from the deepest level all the way up to the top-level reducers, while all the exports in the intermediate reducers are not using them, but they are there because the reducer knows the shape of the state at that level.
It is very much like passing props from parent all the way down to children, while the intermediate component aren't using props. We avoided this by context, or connect.
apologize for the poor English.
So while mapStateToProps does take the entire state tree, it's up to you to return what you'd like from that state in order to render your component.
For instance, we can see he calls getVisibleTodos and passes in state (and params from the router), and gets back a list of filtered todos:
components/VisibleTodoList.js
const mapStateToProps = (state, { params }) => ({
todos: getVisibleTodos(state, params.filter || 'all'),
});
And by following the call, we can see that the store is utilizing combineReducers (albeit with a single reducer), as such, this necessitates that he pass the applicable portion of the state tree to the todos reducer, which is, of course, state.todos.
reducer/index.js
import { combineReducers } from 'redux';
import todos, * as fromTodos from './todos';
const todoApp = combineReducers({
todos,
});
export default todoApp;
export const getVisibleTodos = (state, filter) =>
fromTodos.getVisibleTodos(state.todos, filter);
And while getVisibleTodos returns a list of todos, which by is a direct subset of the top-level state.todos (and equally named as such), I believe that's just for simplicity of the demonstration:
We could easily write another perhaps another component where there's a mapStateToProps similar to:
components/NotTopLevel.js
const mapStateToProps = (state, { params }) => ({
todoText: getSingleTodoText(state, params.todoId),
});
In this case, the getSingleTodoText still accepts full state (and an id from params), however it would only return the text of todo, not even the full object, or a list of top-level todos. So again, it's really up to you to decide what you want to pull out of the store and stuff into your components when rendering.
I also came across this issue (and also had a hard time explaining it...). My solution for compartmentalization this follows from how redux-forms handles it.
Essentially the problem boils down to one issue - where is the reducer bound to? In redux-forms they assume you set it at form (though you can change this) in the global state.
Because you've assumed this, you can now write your module's selectors to accept the globalState and return a selector as follows: (globalState) => globalState.form.someInnerAttribute or whatever you want.
To make it even more extensible you can create an internal variable to track where the state is bound to in the global state tree and also an internal function that's like getStateFromGlobalState = (globalState) => globalState[boundLocation] and uses that to get the inner state tree. Then you can change this variable programatically if you decide to bind your state to a different spot in the global state tree.
This way when you export your module's selectors and use them in mapStateToProps, they can accept the global state. If you make any changes to where the where the reducer is bound, then you only have to change that one internal function.
IMO, this is better than rewriting every nested selector in the top level. That is hard to scale/maintain and requires a lot of boilerplate code. This keeps the reducer/selector module contained to itself. The only thing it needs to know is where the reducer is bound to.
By the way - you can do this for some deeply nested states where you wouldn't necessarily be referring about this from globalState but rather some upper level node on the state tree. Though if you have a super nested state it may make more sense to write the selector from a upper state's POV.

Categories

Resources