DEV Community

Yuya Satake
Yuya Satake

Posted on

How to optimize react rendering

React is the one of the most used framework in terms of Javascript. Some developer may use it without knowing how to optimize rendering.
I believe this article is going to help those people!

React Rendering

Any screen update in a React app happens in three steps:

  • Trigger
  • Render
  • Commit

We have to understand what is going to trigger render and distinguish Render and Commit.
In following, "render" should be recognized as "Render step" of React.

What does trigger component rendering ?

  • state change
  • parent component's rendering

As default behavior of React, when parent component is getting rendered, all child components inside is getting rendered. 
(Not necessarily changes made to the DOM. Diff is caluculate though! )

  • props change

What rendering does

calculate which of their properties have changed since the previous render.

What commit does

apply minimal necessary operations(calculate while rendering) to make the DOM match the rendering output

Optimize rendering

We don't have to think render is bad thing because it is the opportunity that React knows whether commit is needed or not.
But it is good to optimize rendering!

Let's say we are creating the component called ParentComponent which holds the ShowScore Component as child component.
Parent Component has two states to store score and has two function just call setState function and increment the state.
This component receive theme as props to change className.


    function ParentComponent({theme}){
        const [scoreA,setScoreA] = useState(0);
        const [scoreB,setScoreB] = useState(0);

        const changeScoreA =()=>{
            setScoreA(prev => prev + 1);
        }

        const changeScoreB =()=>{
            setScoreB(prev => prev + 1);
        }


        return (
            <div className={theme}>
            <ShowScore who={"A"} score={scoreA} changeFn={changeScoreA} />
            <ShowScore who={"B"} score={scoreB} changeFn={changeScoreB} />
            </div>

        )
    }

Enter fullscreen mode Exit fullscreen mode

In ShowScore component, we received score, who, and changeFn as props.
we use useEffect to check the component is rendered.

    function ShowScore({score,who,changeFn}){
        useEffect(()=>{
           console.log(`renderd: ${who} with ${score}`)
        })

        return (
            <>
                <p>{who} - {score}</p>
                <button onClick={changeFn}>change</button>
            </>

        )
    }
Enter fullscreen mode Exit fullscreen mode

Let's see what happens when we change the theme which is passed to ParentComponent.

As I mentioned above, React re-render a component when props is changed.
And when parent component is rendered, child component inside is also rendered.

With theme changed, ParentComponent is re-rendered and ShowComponent is also re-rendered even though props passed to ShowScore is not changed.

change-theme

We don't have to re-render, right?

In such case, we can use React.memo. This allow us to prevent re-render unless props is changed.

Let's wrap ShowScore component with React.memo.

  const ShowScore = memo(function ShowScore({score,who,changeFn}){
        //...
    })  
Enter fullscreen mode Exit fullscreen mode

change-theme

Same things happened!! It is not suppose to be re-rendered because props passed to ShowScore is not changed.
Why did this happen ?

The answer is that functions defined within the component will re-defined when component is re-rendered and the reference of function will change.\
The function is no longer same even though content inside the function is same!

To solve this problem, we can use useCallback hook. This allows us to cache the function between renders.

Let's wrap the function with useCallback!

     function ParentComponent({theme}){
        //...
        const changeScoreA =useCallback(()=>{
                setScoreA(prev => prev + 1);
            },[])
        const changeScoreB =useCallback(()=>{
            setScoreB(prev => prev + 1);
        },[])
        //...
     }
Enter fullscreen mode Exit fullscreen mode

We have to pass the dependency array as second parameter of useCallback hook. Only when the dependency is changed, function is re-created.\
In this case, we can use empty dependency array because we don't have the variable which changes function's content.

Now that we warped function with useCallback, ShowScore component is not re-rendered when we change theme!

useCallback

Next, let's click the change button. Before that, let's add another function to ParentComponent


 function ParentComponent({theme}){
    //...
        const  isPassed = ()=>{
                while(i < 1000000) i++;
                return scoreA > 2
            }
        }
     return (
            <div className={theme}>
            <ShowScore who={"A"} score={scoreA} changeFn={changeScoreA} />
            <span>{isPassed() && "passed"}</span> // this line is also added 
            <ShowScore who={"B"} score={scoreB} changeFn={changeScoreB} />
            </div>

        )

Enter fullscreen mode Exit fullscreen mode

It's kind of strange, but let's say we check only scoreA and if it is more than 2, show "passed".
And we create isPassed function and make it taking long time to see what happens.

isPassed

Once we click the change button next to A label, score is changing with taking a lot of time.
The same can be said for B even though we don't have to check B's score.

In this case, we can use useMemo hook. This allows us cache the result of a calculation between re-renders.

Let's use useMemo.

function ParentComponent({theme}){
    //...
    const  isPassed = useMemo(()=>{
        let i =0;
        while(i < 1000000000) i++;
        if(scoreA >= 2 ) return true
       },[scoreA])

        return (
            <div className={theme}>
            <ShowScore who={"A"} score={scoreA} changeFn={changeScoreA} />
            <span>{isPassed && "passed"}</span> // removed ()
            <ShowScore who={"B"} score={scoreB} changeFn={changeScoreB} />
            </div>
        )
Enter fullscreen mode Exit fullscreen mode

We have to pass the dependency array to the useMemo function as second argument same as useCallback function.
In this case, we can pass scoreA as dependency and only when scoreA is changed, callback function is executed and returns value.

Let's see what happened with useMemo!

useMemo

As soon as change button next to B is clicked, score is incremented!

you can check out my code here!

Reference

Top comments (0)