React.memo and shallow comparision - javascript

I was reading the following article on React.memo
https://blog.bitsrc.io/optimize-your-react-app-with-react-memo-ec52447b09ba
I had 2 specific questions around the article.
As per the link, it says "In function components React only performs two optimizations by default. First, it avoid the re-render process if by shallow comparison the new state is equal to the old state. Second, it only updates the DOM nodes which have changed and not the whole DOM as updating DOM is costly."
I was confused when it says React by default, compares the state. My understanding is that happens only if we use React.memo. Am I missing something here ?
As per the Shallow Comparison example shown on the site, it seems to suggest different behavior for objects v/s arrays. Not sure if that is correct as well. I thought both arrays/objects would get a new reference each time and hence shallow comparison would return false every time for them ?
Specifically, this example on the link confused me;
const car1 = {
color: 'red',
model: 'S',
};
const car2 = {
color: 'red',
model: 'X',
};
const car3 = {
color: 'red',
model: 'S',
};
shallowCompare(car1, car2); // false
shallowCompare(car1, car3); // true - Why would this return true ???
const arr1 = [1];
const arr2 = [1];
const arr3 = arr1;
console.log(arr1 === arr2); // false - Why is this different compared to object behavior ?
console.log(arr1 === arr3); // true
Also, from my end I tried using below custom function for shallow compare and observed both object and array to behave similarly. This function is not part of the article link above.
function areEqualShallow(a, b) {
for(var key in a) {
if(!(key in b) || a[key] !== b[key]) {
return false;
}
}
for(var key in b) {
if(!(key in a) || a[key] !== b[key]) {
return false;
}
}
return true;
}

Let's start with the fact that the most reliable source is what published by React team.
I was confused when it says React by default, compares the state. My understanding is that happens only if we use React.memo. Am I missing something here?
It's true, you can't customize how you compare state in React.
Although there are workarounds they don't consider "by default", for example by saving the previous state in a reference and conditionally changing state by it:
useEffect(() => {
if (areEqualCustom(prevState.current, newState)) {
setState(newState);
prevState.current = newState;
}
}, [newState]);
As per the Shallow Comparison example shown on the site, it seems to suggest different behavior for objects v/s arrays. Not sure if that is correct as well. I thought both arrays/objects would get a new reference each time and hence shallow comparison would return false every time for them?
You are right, the author's definition for "shallow comparison" is wrong, because "shallow comparison" is defined by the Strict equality (===) operator.
// Blog example
const car1 = {
color: 'red',
model: 'S',
};
const car3 = {
color: 'red',
model: 'S',
};
// car1 === car3
shallowCompare(car1, car3) // always false
// React example
const onClick = () => {
// Don't mutate state in React.
stateObject.x = 5;
// prevState === stateObject (true)
setState(stateObject); // no render
// Instead use Object.assign / shallow copy
setState({ ...stateObject, x: 5 }); // always render
};
Note that in the last example, even if the previous state and the current state are deeply equal it still will rerender.
setState({ x: 5 }); // always render
// even if prevState = { x: 5 }
[1] === [1] Why is this different compared to object behavior ?
It's not, it's the same behavior.

Related

What is the reason I have to use spread operator in React hooks?

Expected behavior
To rendered counter increase by one on each click
Actual behavior
Rendered counter doesn't increase, but internal does (as seen by alert messages). And look at this screenshot: http://prntscr.com/ujtsu9 (it doesn't happen all the time..)
Code
import React, { Component, useState } from "react";
import "./App.css";
function App(props) {
const [all, setAll] = useState([{ name: "PHP", votes: 1 }]);
function changeAll() {
const newAll = all;
alert(newAll === all); //alert true
newAll[0].votes++;
setAll(newAll);
alert(all[0].votes);
}
return (
<>
<div className="voteCount">{all[0].votes}</div>
<div className="lanugageName">{all[0].name}</div>
<button onClick={changeAll}>Click Here</button>
</>
);
}
export default App;
What I have tried:
changing the line const newAll = all; -> to -> const newAll = [...all];
Then it works. But, then alert says 'false'
Question
Why do I need to use spread operator?
Why is newAll === all false if I use spread operator?
Why do I need to use spread operator?
You don't need to use the spread syntax *, but you do need to shallowly copy existing state you intend to mutate into new object/array/memory references. Using the spread syntax is one of the ways to achieve this. When working with array is it also common to use many of the array functions that return new array, i.e. map, filter, slice.
Why is newAll === all false if I use spread operator [syntax *]?
When you const newAll = all you are saving the reference to the state all to newAll as well, but you const newAll = [...all] you are first spreading the state into a new array reference then saving it to newAll
Try it out
const all = [1,2,3];
const newAll1 = all;
const newAll2 = [...all];
console.log(newAll1 === all); // true
console.log(newAll2 === all); // false
* Spread syntax
I want to also caution you against patterns like this
newAll[0].votes++;
setAll(newAll);
Even though you shallowly copied all, newAll[0].votes++ would still be considered a state mutation. If you need to update an element of an array in react state then you should also shallowly copy the properties of any object you intend to update.
setAll(all.map((el, index) => !index ? el : { ...el, votes: el.votes + 1 }))
More importantly, if any state update depends on existing state (incrementing a count is the default example, BTW) you would want to use a functional update.
setAll(all => all.map((el, index) => !index ? el : { ...el, votes: el.votes + 1 }))

How to check for deeply nested props

I often find myself having to build long chains before mapping over an array to check if it's defined:
this.props.photos &&
this.props.photos.activePhotos &&
this.props.photos.activePhotos.map(...
If I leave out the this.props.photos && and this.props.photos.activePhotos.length && my entire application will crash if photos or activePhotos is undefined.
Is there a way to check for these props without having to check every parent object/array of my end item?
January 2020 Update
According to the TC39 proposal, optional chaining will be available shortly within the JavaScript standard (currently in stage 4).
The syntax will be the following :
const active = this.props?.photos?.activePhotos
Or the following in your case :
(this.props?.photos?.activePhotos || []).map(...
While this is being implemented, you may want to take a look at Typescript or js compilers to try out the latest ES features
Old answer
An alternative could be to use a default value for your props when deconstructing them :
const { photos = {} } = this.props
const { activePhotos = [] } = photos
activePhotos.map(/* */)
In this case, if photos is not defined, it will be replaced with an empty object. Trying to get the activePhotos out of it will give you an empty array, allowing you to map on it in any case.
I guess you refer to optional chaining, which is stage 1 of TC39
https://github.com/tc39/proposal-optional-chaining
EDIT: The proposal is now in stage 4 (as of January 2020) and will be added into the JavaScript standard
I'm seeing two possible approaches, according to the level of nesting.
#1. If you have many nested props levels:
You can use lodash.get.
Here's how to render activePhotos, only if they exists:
// Please note how do we pass default `[]` as third parameter
// in order to not break the `.map` function
_.get(this.props, 'photos.activePhotos', []).map(...)
If you only want to check for deeply nested pros, then you can use lodash.has method:
// Will return `true` / `false`
_.has(this.props, 'photos.activePhotos')
#2. If the level of nesting is no more of 2-3 levels:
Just use the native ES6 destructuring assignment + default value feature.
const { photos = {} } = this.props
const { activePhotos = [] } = photos
// Now you can safely map over the `activePhotos`
activePhotos.map(...)
Is there a way to check for these props without having to check every
parent object/array of my end item?
In general, no.
It is not clear why the .length of the potential is checked at the code at the question.
If the goal is to reduce the code length you can us JSON.stringify() and RegExp
if (/"activePhotos":\[.*\]/.test(JSON.stringify(this.props))) // do stuff
or if the preferred approach is using AND operator
/"activePhotos":\[.*\]/.test(JSON.stringify(this.props)) && // do stuff
undefsafe is a good enough library to use. There are lot of other libraries available as well.
Simple example of how to use it
var object = {
a: {
b: {
c: 1,
d: [1,2,3],
e: 'remy'
}
}
};
console.log(undefsafe(object, 'a.b.e')); // "remy"
console.log(undefsafe(object, 'a.b.not.found')); // undefined
Here is a functional approach to optional chaining with default value return. The method uses a Maybe monad and a Proxy.
A wrap() function is used to wrap objects on which you can access any property safely. Internally, wrap creates a Proxy around your object and manages missing values using a Maybe wrapper. At the end of the chain, you unwrap the value by chaining getOrElse(default) with a default value which is returned when the propertie access fails:
class Maybe {
constructor(value) {
this.__value = value;
}
static of(value){
if (value instanceof Maybe) return value;
return new Maybe(value);
}
getOrElse(elseVal) {
return this.isNothing() ? elseVal : this.__value;
}
isNothing() {
return this.__value === null || this.__value === undefined;
}
map(fn) {
return this.isNothing()
? Maybe.of(null)
: Maybe.of(fn(this.__value));
}
}
function wrap(obj) {
function fix(object, property) {
const value = object[property];
return typeof value === 'function' ? value.bind(object) : value;
}
return new Proxy(Maybe.of(obj), {
get: function(target, property) {
if (property in target) {
return fix(target, property);
} else {
return wrap(target.map(val => fix(val, property)));
}
}
});
}
const obj = { a: 1, b: { c: [4, 1, 2] }, c: () => 'yes' };
console.log(wrap(obj).a.getOrElse(null)) // returns 1
console.log(wrap(obj).a.b.c.d.e.f.getOrElse(null)) // returns null
console.log(wrap(obj).b.c.getOrElse([])) // returns [4, 1, 2]
console.log(wrap(obj).b.c[0].getOrElse(null)) // returns 4
console.log(wrap(obj).b.c[100].getOrElse(-1)) // out-of-bounds index: returns -1
console.log(wrap(obj).c.getOrElse(() => 'no')()) // returns 'yes'
console.log(wrap(obj).d.getOrElse(() => 'no')()) // returns 'no'
See this blog post and this link for more information on Maybe monads and the use of proxies.

How to handle calling functions on data that may be undefined?

I primarily work with React and often find that when I write a function that relies on a component's state, I have to perform a check to see if the piece of state is defined before performing any actions.
For example: I have a function that uses .map() to loop over an array of objects fetched from a database and generates jsx for each object in the array. This function is called in the render() function of my component. The first time render() is called, the initial array is empty. This results in an error because, of course, the first index of the array is undefined.
I have been circumventing this by making a conditional check to see if the value of the array is undefined or not. This process of writing an if statement each time feels a little clumsy and I was wondering if there is a better way to perform this check or a way to avoid it entirely.
Check the array before using map:
arr && arr.map()
OR,
arr && arr.length && arr.map() // if you want to map only if not empty array
OR,
We can even use like this (as commented by devserkan):
(arr || []).map()
As per your comment:
I wish there was a safe navigation operator like with C# (arr?.map())
Yes, obviously. This is called optional chaining in JavaScript which is still in proposal. If it is accepted, then you may use like this:
arr?.map()
You can see it in staging 1 for which you may use babel preset stage1
But obviously, except the checking array length, your requirement will not be fulfilled:
This results in an error because, of course, the first index of the array is undefined.
So, I suggest you to use:
arr && arr.length && arr.map()
What you actually need here is called optional chaining:
obj?.a?.b?.c // no error if a, b, or c don't exist or are undefined/null
The ?. is the existential operator and it allows you to access properties safely and won't throw if the property is missing. However optional chaining is not yet part of JavaScript but has been proposed and is in stage 3, see State 3 of TC39.
But, using proxies and a Maybe class, you can implement optional chaining and return a default value when the chain fails.
A wrap() function is used to wrap objects on which you want to apply optional chaining. Internally, wrap creates a Proxy around your object and manages missing values using a Maybe wrapper.
At the end of the chain, you unwrap the value by chaining getOrElse(default) with a default value which is returned when the chain is not valid:
const obj = {
a: 1,
b: {
c: [4, 1, 2]
},
c: () => 'yes'
};
console.log(wrap(obj).a.getOrElse(null)) // returns 1
console.log(wrap(obj).a.b.c.d.e.f.getOrElse(null)) // returns null
console.log(wrap(obj).b.c.getOrElse([])) // returns [4, 1, 2]
console.log(wrap(obj).b.c[0].getOrElse(null)) // returns 4
console.log(wrap(obj).b.c[100].getOrElse(-1)) // returns -1
console.log(wrap(obj).c.getOrElse(() => 'no')()) // returns 'yes'
console.log(wrap(obj).d.getOrElse(() => 'no')()) // returns 'no'
wrap(obj).noArray.getOrElse([1]).forEach(v => console.log(v)) // Shows 1
wrap(obj).b.c.getOrElse([]).forEach(v => console.log(v)) // Shows 4, 1, 2
The complete example:
class Maybe {
constructor(value) {
this.__value = value;
}
static of(value){
if (value instanceof Maybe) return value;
return new Maybe(value);
}
getOrElse(elseVal) {
return this.isNothing() ? elseVal : this.__value;
}
isNothing() {
return this.__value === null || this.__value === undefined;
}
map(fn) {
return this.isNothing()
? Maybe.of(null)
: Maybe.of(fn(this.__value));
}
}
function wrap(obj) {
function fix(object, property) {
const value = object[property];
return typeof value === 'function' ? value.bind(object) : value;
}
return new Proxy(Maybe.of(obj), {
get: function(target, property) {
if (property in target) {
return fix(target, property);
} else {
return wrap(target.map(val => fix(val, property)));
}
}
});
}
const obj = { a: 1, b: { c: [4, 1, 2] }, c: () => 'yes' };
console.log(wrap(obj).a.getOrElse(null))
console.log(wrap(obj).a.b.c.d.e.f.getOrElse(null))
console.log(wrap(obj).b.c.getOrElse([]))
console.log(wrap(obj).b.c[0].getOrElse(null))
console.log(wrap(obj).b.c[100].getOrElse(-1))
console.log(wrap(obj).c.getOrElse(() => 'no')())
console.log(wrap(obj).d.getOrElse(() => 'no')())
wrap(obj).noArray.getOrElse([1]).forEach(v => console.log(v)) // Shows 1
wrap(obj).b.c.getOrElse([]).forEach(v => console.log(v)) // Shows 4, 1, 2

Using spread operator to update an object value

I have a function that adds a key to incoming object, but I have been told to use the spread operator for that, I have been told that I can use the spread operator to create a new object with the same properties and then set isAvailable on it.
return new Partner(ServerConfig, capabilities, initialState)
}
class Partner {
constructor (ServerConfig, capabilities, initialState) {
initialState.isAvailable = true
So I tried something like this but couldn't succeed, can you help me? and confused, should I use the spread operator in this way, return from a function ?
newObject = {}
// use this inside a function and get value from return
return {
value: {
...newObject,
...initialState
}
}
initialState.isAvailable = true
The properties are added in order, so if you want to override existing properties, you need to put them at the end instead of at the beginning:
return {
value: {
...initialState,
...newObject
}
}
You don't need newObject (unless you already have it lying around), though:
return {
value: {
...initialState,
isAvailable: newValue
}
}
Example:
const o1 = {a: "original a", b: "original b"};
// Doesn't work:
const o2 = {a: "updated a", ...o1};
console.log(o2);
// Works:
const o3 = {...o1, a: "updated a"};
console.log(o3);
If you know the name of the property (a in the example below), then #crowder's answer is perfect:
const o3 = {...o1, a: "updated a"};
console.log(o3);
If the property name is in a variable, then you need to use Computed Property names syntax:
let variable = 'foo'
const o4 = {...o1, [variable]: "updated foo"};
console.log(o4);
Spreading the object usint ... will keep your state integrity. Let's say you have an initial state
initialState={isOpen:false,count:0}
Assume that you want to add another property but you want to keep other properties:
return { ...initialState,isAvailable:true
}
this will lead to
initialState={isOpen:false,count:0,isAvailable:true}
we just added a new property without discarding the other properties. maybe another part of your app is using other properties so we still keep them. let's say you want to update isOpen state. you spread the initial state first and then add the last piece of logic
return { ...initialState,isOpen:true
}
this will lead to this
initialState={isOpen:true,count:0,isAvailable:true}
Now maybe on your current page, since isOpen:true you might be showing a different piece of UI to the user. Let's say you want to update the count this time. You could write this too
return { count:5 }
Now your current state has only count:5 so you have lost other properties in your state. Now there is no isOpen property. If you did not handle the isOpen logic successfully while you were displaying a different UI, your app will crash. but if you handle correctly, isOpen would lead to falsy so your page will not show that piece of UI to the user.

How does shallow compare work in react

In this documentation of React, it is said that
shallowCompare performs a shallow equality check on the current props and nextProps objects as well as the current state and nextState objects.
The thing which I am unable to understand is If It shallowly compares the objects then shouldComponentUpdate method will always return true, as
We should not mutate the states.
and if we are not mutating the states then the comparison will always return false and so the shouldComponent update will always return true. I am confused about how it is working and how will we override this to boost the performance.
Shallow compare does check for equality. When comparing scalar values (numbers, strings) it compares their values. When comparing objects, it does not compare their attributes - only their references are compared (e.g. "do they point to same object?").
Let's consider the following shape of user object
user = {
name: "John",
surname: "Doe"
}
Example 1:
const user = this.state.user;
user.name = "Jane";
console.log(user === this.state.user); // true
Notice you changed users name. Even with this change, the objects are equal. The references are exactly the same.
Example 2:
const user = clone(this.state.user);
console.log(user === this.state.user); // false
Now, without any changes to object properties they are completely different. By cloning the original object, you create a new copy with a different reference.
Clone function might look like this (ES6 syntax)
const clone = obj => Object.assign({}, ...obj);
Shallow compare is an efficient way to detect changes. It expects you don't mutate data.
shallow comparison is when the properties of the objects being compared is done using "===" or strict equality and will not conduct comparisons deeper into the properties. for e.g.
// a simple implementation of the shallowCompare.
// only compares the first level properties and hence shallow.
// state updates(theoretically) if this function returns true.
function shallowCompare(newObj, prevObj){
for (key in newObj){
if(newObj[key] !== prevObj[key]) return true;
}
return false;
}
//
var game_item = {
game: "football",
first_world_cup: "1930",
teams: {
North_America: 1,
South_America: 4,
Europe: 8
}
}
// Case 1:
// if this be the object passed to setState
var updated_game_item1 = {
game: "football",
first_world_cup: "1930",
teams: {
North_America: 1,
South_America: 4,
Europe: 8
}
}
shallowCompare(updated_game_item1, game_item); // true - meaning the state
// will update.
Although both the objects appear to be same, game_item.teams is not the same reference as updated_game_item.teams. For 2 objects to be same, they should point to the same object.
Thus this results in the state being evaluated to be updated
// Case 2:
// if this be the object passed to setState
var updated_game_item2 = {
game: "football",
first_world_cup: "1930",
teams: game_item.teams
}
shallowCompare(updated_game_item2, game_item); // false - meaning the state
// will not update.
This time every one of the properties return true for the strict comparison as the teams property in the new and old object point to the same object.
// Case 3:
// if this be the object passed to setState
var updated_game_item3 = {
first_world_cup: 1930
}
shallowCompare(updated_game_item3, game_item); // true - will update
The updated_game_item3.first_world_cup property fails the strict evaluation as 1930 is a number while game_item.first_world_cup is a string. Had the comparison been loose (==) this would have passed. Nonetheless this will also result in state update.
Additional Notes:
Doing deep compare is pointless as it would significantly effect performance if the state object is deeply nested. But if its not too nested and you still need a deep compare, implement it in shouldComponentUpdate and check if that suffices.
You can definitely mutate the state object directly but the state of the components would not be affected, since its in the setState method flow that react implements the component update cycle hooks. If you update the state object directly to deliberately avoid the component life-cycle hooks, then probably you should be using a simple variable or object to store the data and not the state object.
Shallow compare works by checking if two values are equal in case of primitive types like string, numbers and in case of object it just checks the reference. So if you shallow compare a deep nested object it will just check the reference not the values inside that object.
There is also legacy explanation of shallow compare in React:
shallowCompare performs a shallow equality check on the current props and nextProps objects as well as the current state and nextState objects.
It does this by iterating on the keys of the objects being compared and returning true when the values of a key in each object are not strictly equal.
UPD: Current documentation says about shallow compare:
If your React component's render() function renders the same result given the same props and state, you can use React.PureComponent for a performance boost in some cases.
React.PureComponent's shouldComponentUpdate() only shallowly compares the objects. If these contain complex data structures, it may produce false-negatives for deeper differences. Only extend PureComponent when you expect to have simple props and state, or use forceUpdate() when you know deep data structures have changed
UPD2: I think Reconciliation is also important theme for shallow compare understanding.
The accepted answer can be a bit misleading for some people.
user = {
name: "John",
surname: "Doe"
}
const user = this.state.user;
user.name = "Jane";
console.log(user === this.state.user); // true
This statement in particular "Notice you changed users name. Even with this change objects are equal. They references are exactly same."
When you do the following with objects in javascript:
const a = {name: "John"};
const b = a;
Mutating any of the two variables will change both of them because they have the same reference. That's why they will always be equal (==, ===, Object.is()) to each other.
Now for React, the following is the shallow comparison function:
https://github.com/facebook/fbjs/blob/master/packages/fbjs/src/core/shallowEqual.js
/**
* Performs equality by iterating through keys on an object and returning false
* when any key has values which are not strictly equal between the arguments.
* Returns true when the values of all keys are strictly equal.
*/
function shallowEqual(objA: mixed, objB: mixed): boolean {
if (is(objA, objB)) {
return true;
}
if (typeof objA !== 'object' || objA === null ||
typeof objB !== 'object' || objB === null) {
return false;
}
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
// Test for A's keys different from B.
for (let i = 0; i < keysA.length; i++) {
if (
!hasOwnProperty.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])
) {
return false;
}
}
return
For non-primitives (Objects), it checks:
If the first object is equal (using Object.is()) to the second.
If not, it checks if each key-value pair in the first object is equal (using Object.is()) to that of the second. This is done for the first level of keys. If the object has a key whose value is another object, this function does not check for equality further down the depth of the object.
It took me a while to actually know shallow compare and === are two different thing, especially while reading redux documentation in the following.
However, when an action is dispatched to the Redux store, useSelector() only forces a re-render if the selector result appears to be different than the last result. As of v7.1.0-alpha.5, the default comparison is a strict === reference comparison. This is different than connect(), which uses shallow equality checks on the results of mapState calls to determine if re-rendering is needed. This has several implications on how you should use useSelector().
strict equal
so step by step, the strict equal === is very consistently defined by the Javascript language, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Strict_equality
What it does is to compare two items by value if they are primitive, and then by reference if they are object. Of course if the types of two objects are different, they will never match.
shallow compare
shallow probably isn't a build-in feature of the language. Couple of the answers here pointed us to some variation of the implementation, https://github.com/facebook/fbjs/blob/main/packages/fbjs/src/core/shallowEqual.js
The idea is to compare two items by value if they are primitive. But for non primitives, we go one level lower. For objects, if the keys are different between two objects, we say they are not same. If the value under a key is different, we say they are not same either.
summary
This means, shallow comparison checks more than the strict equal ===, especially when it comes to the object. A quick look might suggest, === does not do too much guess work.
The shallow equal snippet by #supi above (https://stackoverflow.com/a/51343585/800608) fails if prevObj has a key that newObj doesn't have. Here is an implementation that should take that into account:
const shallowEqual = (objA, objB) => {
if (!objA || !objB) {
return objA === objB
}
return !Boolean(
Object
.keys(Object.assign({}, objA, objB))
.find((key) => objA[key] !== objB[key])
)
}
Note that the above doesn't work in Explorer without polyfills.
There is an implementation with examples.
const isObject = value => typeof value === 'object' && value !== null;
const compareObjects = (A, B) => {
const keysA = Object.keys(A);
const keysB = Object.keys(B);
if (keysA.length !== keysB.length) {
return false;
}
return !keysA.some(key => !B.hasOwnProperty(key) || A[key] !== B[key]);
};
const shallowEqual = (A, B) => {
if (A === B) {
return true;
}
if ([A, B].every(Number.isNaN)) {
return true;
}
if (![A, B].every(isObject)) {
return false;
}
return compareObjects(A, B);
};
const a = { field: 1 };
const b = { field: 2 };
const c = { field: { field: 1 } };
const d = { field: { field: 1 } };
console.log(shallowEqual(1, 1)); // true
console.log(shallowEqual(1, 2)); // false
console.log(shallowEqual(null, null)); // true
console.log(shallowEqual(NaN, NaN)); // true
console.log(shallowEqual([], [])); // true
console.log(shallowEqual([1], [2])); // false
console.log(shallowEqual({}, {})); // true
console.log(shallowEqual({}, a)); // false
console.log(shallowEqual(a, b)); // false
console.log(shallowEqual(a, c)); // false
console.log(shallowEqual(c, d)); // false
Very simple to understand it. first need to understand the pure component and regular component, if a component has coming props or state is changing then it will re-rendered the component again.
if not then not.
in regular component shouldComponentUpdate by default true. and in pure component only the time when state change with diff value.
so now what is shallow component or shallow ?
lets take an simple example.
let a = [1,2,3],
let b = [1,2,3],
a == b ==> shallow take it false,
a == c ==> shallow take it true. c has any diff value.
now i think you can understand it. the diff in both regular and pure component with shallow component
if you like it, also do like share and subscribe my youtube channel
https://www.youtube.com/muosigmaclasses
Thanks.
I feel that none of the answers actually addressed the crucial part in your question, the answers merely explain what shallow comparison is (whether they mean the JavaScript default shallow comparison that is a result of the === or == operator or React's shallowCompare() function)
To answer your question, my understanding so far of React makes me believe that yes indeed by not directly mutating the states then shouldComponentUpdate will always return true thus always causing a re-render no matter what objects we pass in setState even if the objects passed to setState hold the same values stored in the current state
example:
Say I have a React.Component with the current state and function:
this.state = {data: {num: 1}} // current state object
foo() { // something will cause this function to called, thus calling setState
this.setState( {data: {num: 1}} ); // new state object
}
You can see that setState passed the same object (value-wise) however plain React is not smart enough to realize that this component shouldn't update/re-render.
To overcome this, you have to implement your version of shouldComponentUpdate in which you apply deep comparison yourself on the state/props elements that you think should be taken into consideration.
Check out this article on lucybain.com that briefly answers this question.

Categories