Is it safe to check object reference in componentWillReceiveProps? - javascript

I have a component connected to redux store and I have a code block like in this:
if (this.props.person !== nextProps.person) {
...
} else {
...
}
...
MyComponent.propTypes {
person: PropTypes.object.isRequired
}
Is it safe to check object reference in this way? Can I assume that in a reducer object reference will always change?

It is safe as long as your reducer is a pure function. To guarantee purity, those are the 3 most important things you should never do inside a reducer:
Mutate its arguments (use Object.assign or the object spread operator to avoid mutations on objects)
Perform side effects like API calls and routing transitions
Call non-pure functions, e.g. Date.now() or Math.random().
If your reducer satisfy all 3 conditions, any particular action dispatched that turned out to modify person property inside the state tree, will result in a new person object.
In that case, this.props.person and nextProps.person would be two different objects and that object reference check would be correct. However if a particular action dispatched didn't modify person property inside the state tree, this.props.person and nextProps.person would still be the same object

Consider a simple reducer:
function reducer(state, action) {
switch(action.type) {
...
default:
return state
}
}
This is a normal part of redux - if your reducer is called with an unknown action, it returns the same state. In this case, this.props.person === nextProps.person, because the object reference has not changed.
If your code (your reducer) changes the reference to person, the reference will have changed in your component as well.

Related

Redux: Is it common to do deep-compare at reducer and return original state if no change?

Action: fetch an array of data from the server.
Reducer: save an array of data to store.
My reducer saves the array of data in the expected immutable fashion:
return {
...state,
arrayOfData: fetchedArrayOfData,
};
The fetch action happens periodically, but the content of the array remains the same most of the time. However, since a new reference is saved each time, the selector for this data will be considered "changed", and doesn't memoize well if used as an input for creatorSelector.
The only solution I can think of is to perform a deep-compare at the reducer, and if the contents are the same, simply return the original state:
return state;
If this common practice/pattern?
Note: I tried to look around, and most projects are doing the same thing that I was doing (i.e. return new state object), which will do not memoize well and causes selector transformations to run.
There are some solutions like using immutableJS as mentioned in the comments but you can return your state conditionally by comparing your fetchedArrayOfData with the last one (which is stored in your state).
Assume there is a comparison function that gives two arrays and compares them.
In the reducer:
const previousFetchedData = state.fetchedArrayOfData;
const newFetchedData = action.payload.fetchedArrayOfData;
const resultOfComparision = isSameArray(previousFetchedData, newFetchedData) // true or false
if (resultOfComparision) { // case of same arrays
return state
} else { // case of different arrays
...state,
arrayOfData: fetchedArrayOfData,
};
Note 1: you can create your own comparison function but there are many nice ones in this post of StackOverflow which you can use them.
Note 2: using immutableJs and conditional returning data from reducer is common(in such scenario) and don't worry about using them.
Note 3: You can also compare your data at the component level by using the traditional way with shouldComponentUpdate.
Node 4: using middlewares like redux-saga will be useful, you can also implement the isSameArray function in the saga and then dispatch the proper action. read more on saga documentation.
Note 5: The best solution (in my thought) is to handle this case on the backend services with 304 status which means Not modified. then you can easily determine the right action according to the response status. more info on MDN documentation.

Should we compare new state with old state in the reducer

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.

Understanding a basic implementation of Redux | how is prevState used?

I'm looking at this example of a super basic implementation of redux from this article. I understand it except where dispatch uses prevState. First off where does this function get prevState? How is this related to the actually state that counter needs? Does it implicitly set another value in the state called prevState? I'm just having a hard time understanding how the state actually gets passed to dispatch and then counter through prevState. I think this is probably a functional programming idea that I just haven't grasped yet. Thanks for helping me understand!
import React, { Component } from 'react';
const counter = (state = { value: 0 }, action) => {
switch (action.type) {
case 'INCREMENT':
return { value: state.value + 1 };
case 'DECREMENT':
return { value: state.value - 1 };
default:
return state;
}
}
class Counter extends Component {
state = counter(undefined, {});
dispatch(action) {
this.setState(prevState => counter(prevState, action));
}
increment = () => {
this.dispatch({ type: 'INCREMENT' });
};
decrement = () => {
this.dispatch({ type: 'DECREMENT' });
};
render() {
return (
<div>
{this.state.value}
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
</div>
)
}
}
React.Component.prototype.setState
Well react's setState method is responsible for prevState here – not redux, just to be clear.
Let's look at how setState can be used. We have 3 options ...
setState(nextState) - just pass the next state and React will update the components state accordingly
setState(nextState, callback) – optionally specify a callback that is called once the component is re-rendered – a React component will re-render whenever state is updated – Note: React docs generally recommend to use componentDidUpdate lifecycle method for this instead.
setState((prevState, props) => nextState) – call with a function that conveniently binds prevState and props identifiers for us to manually update our state. It's expected that the function we provide returns the next state
So you have this
this.setState(prevState => counter(prevState, action));
If it's not obvious, the code example you've provided is using #3. So, the first parameter prevState will be provided by React which is the current state of the component. The second parameter props is not used here, so we'll just ignore it (tho the general idea is you could use props to update your state if relevant).
What is prevState?
What exactly is prevState? Well we established that it's the current state of your component, which we initialized earlier in the Counter as
state = counter(undefined, {});
// => { value: 0 }
So when we dispatch an INCREMENT we will get something along the lines of
this.setState(prevState => counter({value: 0}, {type: 'INCREMENT'})
In which case counter (reducer) will return
{value: 1}
This return value is what will be the next state of the Component
Repeated applications of setState
Of course if we were to INCREMENT again, we'd have something like
this.setState(prevState => counter({value: 1}, {type: 'INCREMENT'})
Where counter would return
{value: 2}
which becomes the next state of the Component
and so on..
"Where is line between React and Redux?"
To start, the React-specific code is the import and the Counter class which extends Component.
"But what is the other code (counter) then?"
One of the coolest things about redux is its state of nothingness – Redux only exists in this code example as a pattern. There's no import which uses the redux or react-redux. Redux is used here more as an implementation of the idea/philosophy of redux – which is built around this idea of unidirectional flow of data and composable reducers.
"What is a reducer?"
A reducer is simply a function which, when applied to a state and an action, returns a new state.
Of course the redux library includes some helpful utilities which make it easier to implement Redux's patterns within your application – but really, they're all incredibly simple functions. In fact, the creator of Redux, Dan Abramov, has an awesome (FREE) series on egghead, Getting Started with Redux which shows you exactly how Redux works, piece by piece. In my opinion, it is one of the best coding video series ever created, on any topic.
prevState in this case is an actual, current state. It's not traveling back in time, it simply returns your current state, which is going to be used in order to build a new one - because in Redux and React's concept, the state is immutable, that means it never gets modified - when you dispatch new action and handle it with your reducer - you're creating completely new object (state).
First, note that the Class Counter extends from React's Component type. Because of this it will inherit a bunch of properties and methods, one of which is setState.
We can see from the React documentation for setState that it takes two arguments:
setState(nextState, callback)
But in the fine print it says this: "The first argument can be an object (containing zero or more keys to update) or a function (of state and props) that returns an object containing keys to update."
In the case here, we are only passing one argument, so we must be using it with the first argument being a function that returns an object of keys to update.
If we take another look at the original code where setState is used:
this.setState(prevState => counter(prevState, action));
It might be a little easier to read and understand if we wrote this in es5 JavaScript syntax:
this.setState(
function cb(prevstate) {
return counter(prevstate, action)
})
So in this case "prevState" is an argument to an anonymous function. In theory, it could be named anything and as long as you use the same name to refer to it inside on the function body everything will be fine. However, it seems like a pretty standard thing to name it "prevState" (that's what the React docs use).
If we look at this as purely JavaScript the prevState that you are passing into this function should be undefined.
Therefore, firing the INCREMENT action multiple times should result in your state value always being 1 since the counter function will always use the default value:
state = { value: 0 }
I would think the dispatch function should look like to this:
dispatch(action) {
this.setState(counter(this.state, action));
}
UPDATE
This link may explain it better, but indeed if you use a function as the first argument for setState then this function will take the current state as its argument, and so that is how prevState gets its value.

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
}

Is it a good practice to modify a component's state and then call "setState(this.state)"?

I'm using ReactJS.
I have a stateful component (stopwatch container) and multiple child components which are stateless (stopwatches).
In the outer component, I'm doing something like this:
// the outer component is a "container" for multiple stopwatches
tick: function() {
for (var i = 0; i < this.state.stopwatches.length; i++)
{
// if a particular stopwatch is "started", I'll increase it's time
if (this.state.stopwatches[i].status == "started")
{
this.state.stopwatches[i].time.seconds++;
// do some processing
}
}
// this is the suspicious code!
this.setState(this.state);
}
Notice that I'm changing the this.state property and then calling setState() on the state object itself. This seems so wrong to me. But, in the other hand, in order to not manipulate the state object itself, i'd have to clone it and then do setState(stateClone), but I'm not sure if it's possible to clone objects effectively in JS, neither if I really should do this.
Can I continue to do setState(this.state) ?
Instead of calling this.setState(this.state) you can just call this.forceUpdate().
Just remember that this is not recommended way of updating components state. If you are unsure about your modifications take a look on Immutability Helpers.
Just my two cents here: React will re-render every time you call setState:
boolean shouldComponentUpdate(object nextProps, object nextState)
returns true by default. If you don't wish to re-render make shouldComponentUpdate(object nextProps, object nextState) to return false.
Quoting from Facebook docs:
By default, shouldComponentUpdate always returns true to prevent
subtle bugs when state is mutated in place, but if you are careful
to always treat state as immutable and to read only from props and
state in render() then you can override shouldComponentUpdate
with an implementation that compares the old props and state to
their replacements.
Aside from immutability (which is always good), the second best would be this.setState({stopwatches: this.state.stopwatches}).
With immutability helpers, it'd look like this:
var stopwatches = this.state.stopwatches.map(function(watch){
return update(watch, {
time: {$set: watch.time + 1}
});
});
this.setState({stopwatches: stopwatches})
Or with es6 you can save a few characters.
var stopwatches = this.state.stopwatches.map(watch => update(watch, {
time: {$set: watch.time + 1}
});
this.setState({stopwatches})

Categories

Resources