DEV Community

Kaziu
Kaziu

Posted on

📖 History of "Stop unnecessary re-rendering component in React !!"

once upon time...

💎 Generation of class component

🚩 Pure component()

Compare new and old props/state, if there is no difference between them, component renders

compare?? but how to compare them??

<< the case of rendering in React >>

  1. state changes
  2. parent component renders
  3. props changes
  4. shouldcomponentUpdate function returns true (I'll explain about it later)
  5. forceUpdate

for numbers 1 and 2, React decides whether to render through shallow compare

What is shallow compare?

At first, we need to get what is reference
from this website
Image description

  1. pass by reference (shallow copy)
    If you pour coffee in copied cup, original cup is also filled with it (because both datas are in same memory allocation space)

  2. pass by value (deep copy)
    If you pour coffee in copied cup, original cup is still empty

in Javascript, primitive data type (String, Number, Bigint, Boolean, Undefined, Symbol) is pass by value, and Object, Array is pass by reference

honestly comparing with primitive data type is not so difficult, but we need to care about comparing with Object

the case of object reference is the same

import shallowCompare from 'react-addons-shallow-compare';

const a = { country: "poland", country2: "japan" }
const b = a

console.log(shallowEqual(a, b))
// true
Enter fullscreen mode Exit fullscreen mode

the case of object reference is different

  1. not nested object
import shallowCompare from 'react-addons-shallow-compare';

const a = { country: "poland", country2: "japan" }
const b = { country: "poland", country2: "japan" }

console.log(shallowEqual(a, b))
// true
Enter fullscreen mode Exit fullscreen mode
  1. nested object
import shallowCompare from 'react-addons-shallow-compare';

const a = {
  country: "poland",
  coountry2: {
    city1: "tokyo",
    city2: "osaka"
  }
}

const b = {
  country: "poland", // country is primitive type, scalar data is the same -> true
  country2: { // country2 is object, so reference is different -> false
    city1: "tokyo",
    city2: "osaka"
  }
}

console.log(shallowEqual(a, b))
// ⭐ false
Enter fullscreen mode Exit fullscreen mode

🚩 shouldComponentUpdate()

👦 so it is ok all components are pure component, isn't it?
👩‍💻 no, because cost of comparing old and new state/props is high
👦 what should I do then?
👩‍💻 just decide comparing condition by yourself via "shouldComponentUpdate()"

actually PureComponent is like component which is implemented by someone(would be someone in facebook company) through shouldComponentUpdate()

// something like that
class PureComponent extends React.Component {
    shouldComponentUpdate(nextProps, nextState) {
        return !(shallowEqual(this.props, nextProps) && shallowEqual(this.state, nextState));
    }
    
}
Enter fullscreen mode Exit fullscreen mode

💎 Functional Component generation

2022 we are in this generation

🚩 React.memo

it is like PureComponent() + shouldComponentUpdate()

// if new props changes, this component will be rendered
const Button = React.memo(props => {
    return <div>{props.value}</div>
})
Enter fullscreen mode Exit fullscreen mode
// if you put second argument, it is like shouldComponentUpdate()
const Button = React.memo(
    props => {
        return <div>{props.value}</div>
    },
    (nextProps, prevProps) => {
        return nextProps.value === prevProps.value
    }
)
Enter fullscreen mode Exit fullscreen mode

🚩 useMemo

👦 what is useMemo? it is the same of React.memo?
👩‍💻 no, similar though. React.memo is added by React version16.6, and then React hook is added by version 16.8, useMemo as well.
👦 aha
👩‍💻 useMemo renders only when props changes because it remembers calculation result

// when only "products props" changes, this component renders
const Component: React.FC = ({ products }) => {
    const soldoutProducts = React.useMemo(() => products.filter(x => x.isSoldout === true), [products])
}
Enter fullscreen mode Exit fullscreen mode

🚩 useCallback

When parent component passes props of function to child component, new function (actually function is just one of object) is made.
Because of it child component recognize this new function is different from old one, then re-renders sadly.

↓ conversation between child/parent component

👨 Parent「re-renderiiingggg!! And now I recreated function that I have !!」
👼 Child「Mom!! Give me your function as props!!」
👨 Parent「ok I give you my function!」
👼 Child「well now I need to confirm this object’s memory address is the same as object that I got before …. Hmmm different address, I also re-rendering!!!」

to prevent this unnecessary re-rendering, should use useCallback

Discussion (12)

Collapse
thuutri2710 profile image
Tris

and Object, Array is pass by reference
I dont think it's actually true. In JS, we only have pass-by-value. Pass-by-reference means we will pass the ADDRESS of VARIABLE. But in JS, we pass the object which variable is referring. So it's not pass-by-reference

Collapse
jorge_rockr profile image
Jorge Ramón

Everything in JavaScript is pass-by-reference so if your function modifies an object or array then the change applies to the original object.

The case of the "pass-by-value" in primitive date types it's because primitive data types are immutable so yeah, you are still passing the reference to a function but it doesn't matter cause its immutability

Collapse
thuutri2710 profile image
Tris
Thread Thread
jorge_rockr profile image
Jorge Ramón

Thanks, exactly what I said but remember primitives are immutable so that why they act as "pass-by-value"

Thread Thread
thuutri2710 profile image
Tris

Oh, let me confirm again. In JS, we only "pass-by-value"

Thread Thread
jorge_rockr profile image
Jorge Ramón

Okay, I got your point.

Technically, JS works "pass-by-value" since you pass a copy of a reference. But if you modify that copy of the reference then it modifies the original object so that's basically "pass-by-reference" xd

Same as Primitive Data Types Immutability is basically the same as pass-by-value.

So it depends, you can see the glass half empty or half full

Thread Thread
thuutri2710 profile image
Tris

Agree with you

Collapse
alienjedi profile image
Andrews Kangah

I think there's problem with React.memo example. Change determination function should be like the following instead...
return nextProps.value !== prevProps.value

Not like the following...
return nextProps.value === prevProps.value

Because it should re-render only when old value not equal to new value

Collapse
daneelf profile image
Danae

Very informative! Would be nice to have an example for useCallback too though! It is not very clear to me :/

Collapse
rainson12 profile image
Rainson12

+1 for the example of the useCallback. Would appreciate it.

Collapse
atomeistee profile image
AtomEistee

Amazing explanations, but my small problem is that i quite dont understand when exactly do i need one of this things, i guess i will understand it with time cuz i think there is no universal rule for each website. Thanks for this article.

Collapse
taowen profile image
Tao Wen

re-rendering could also be caused by react context, it can skip your parent and directly re-render some child