Arp
Arp

Reputation: 1088

How to prevent unnecessary renders in a Component using useContext?

I would like to know how can I avoid unnecessary updates on a child component that does not receive props, but uses useContext.

I've added React.memo to the Child2 component for me to make some demo tests.

As you can see, when I change the input value which uses context to pass the data to child1, Child2 does not re-render, as React.memo prevents this behavior.

How can I prevent unnecessary renders in Child1 as well? I know that React.memo won't work as it needs props, and with context this will not happen, as no props are being passed down to this component

App.js

import React, {useState} from 'react';
import './App.css';
import Child1 from "./Child1";
import Child2 from "./Child2";
import SillyContext from './context'

function App() {
    const [name, setName] = useState('Radha');
    const [count, setCount] = useState(0)

    const increaseCount = () => {
        setCount(prevState => prevState + 1)
    }

    return (
        <div className="App">
            <Child2 count={count}/>
            <button onClick={increaseCount}>Increase</button>
            <input type="text" value={name} onChange={(event => setName(event.target.value))}/>
            <SillyContext.Provider value={{name}}>
                <Child1/>
            </SillyContext.Provider>
        </div>
    );
}

export default App;

Child1

import React, {useContext} from "react";
import SillyContext from './context'

export default function Child1() {
    const sillyContext = useContext(SillyContext)
    console.log('[Child 1 ran]')
    return (
        <div>
            <div>[Child 1]: {sillyContext.name}</div>
        </div>
    )
}

Child2

import React from 'react'

export default React.memo(function Child2(props) {
    console.log('[Child2 Ran!]')
    return <div>[Child2] - Count: {props.count}</div>
})

Upvotes: 0

Views: 327

Answers (2)

Shubham Khatri
Shubham Khatri

Reputation: 281676

The major problem due to which Child1 re-renders when count is updated is because you are passing a new object reference to Context Provider everytime.

Also If the App component re-renders, all element rendered within it re-render, unless they implement memoization or are PureComponent or use shouldComponentUpdate

You can make 2 changes to fix your re-rendering

  • wrap Child1 with React.memo
  • Use useMemo to memoize the object passed as value to provider

App.js

function App() {
  const [name, setName] = useState("Radha");
  const [count, setCount] = useState(0);

  const increaseCount = () => {
    setCount(prevState => prevState + 1);
  };

  const value = useMemo(() => ({ name }), [name]);
  return (
    <div className="App">
      <Child2 count={count} />
      <button onClick={increaseCount}>Increase</button>

      <input
        type="text"
        value={name}
        onChange={event => setName(event.target.value)}
      />
      <SillyContext.Provider value={value}>
        <Child1 />
      </SillyContext.Provider>
    </div>
  );
}

Child2

const Child2 = React.memo(function(props) {
  console.log("[Child2 Ran!]");
  return <div>[Child2] - Count: {props.count}</div>;
});

Child 1

const Child1 = React.memo(function() {
  const sillyContext = useContext(SillyContext);
  console.log("[Child 1 ran]");
  return (
    <div>
      <div>[Child 1]: {sillyContext.name}</div>
    </div>
  );
});

Working demo

Upvotes: 1

J&#243;zef Podlecki
J&#243;zef Podlecki

Reputation: 11283

You can use useMemo inside Child1 component. It doesn't solve rerendering problem, but it is some kind of improvement.

Following snippet is third variant proposed by React core dev

we could make our code a bit more verbose but keep it in a single component by wrapping return value in useMemo and specifying its dependencies. Our component would still re-execute, but React wouldn't re-render the child tree if all useMemo inputs are the same.

const { useState, useEffect, createContext, useContext, useMemo } = React;

const SillyContext = createContext();

const Child2 = React.memo(({count}) => {
    return <div>Count: {count}</div>
})         

const Child1 = () => {
    const {name} = useContext(SillyContext);
    
    return useMemo(() => {
    console.log('Child1');
      return <div>
        <div>Child 1: {name}</div>
      </div>}, [name])
}

const App = () => {
  const [count, setCount] = useState(0);

  const increaseCount = () => {
        setCount(prevState => prevState + 1)
  }

return <div>
  <Child2 count={count}/>
   <button onClick={increaseCount}>Increase</button>
  <Child1/>
</div>
}

ReactDOM.render(
    <SillyContext.Provider value={{name: 'test'}}>
      <App />
    </SillyContext.Provider>,
    document.getElementById('root')
  );
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<div id="root"></div>

Upvotes: 0

Related Questions