Home > Software engineering >  Render only components with changes
Render only components with changes

Time:01-06

I have an array with thousands of strings and is passed to a component:

Main component:

const array = ['name1', 'name2', 'name3'];
const [names, setNames] = useState(array);

const onClick = (index) => {
  setNames(names.map((name, i) => {
    if (i === index) {
      return 'name changed';
    }
  }; 
};

return (
  <ul>
    {array.map((name, index) => (
      <li key={index}>
        <ShowName name={name} key={index} onClick={() => onClick(index)} />
      </li>
    )}
  </ul>
);

ShowName component:

let a = 0;

export default function ShowName({ name, onClick }) {
  a  = 1;
  console.log(a);

  return (
    <button type="button" onClick={onClick}>{name}</button>
  );
}

There's also a button which changes a name randomly. But whenever the button is pressed, all the ShowName components are rerendering. I've been trying to use useCallback and useMemo, but the components are still rerendering x times (x is the length of the array).

const ShowNameHoc = ({ name }) => {
  return <ShowName name={name} />
};

return (
  <div>
    {array.map((name, index) => <ShowNameHoc name={name} key={index} />)}
  </div>
);

What should I do if I only want to rerender the component with a change?

CodePudding user response:

You have a misunderstanding in the concepts here. The default is for React to call render on all children, regardless of whether the props changed or not.
After that happened, React will compare that new Virtual DOM to the current Virtual DOM and then only update those parts of the real DOM that changed. That's why the code in a render method should be quick to execute.

This behavior can be changed by using features like useMemo, PureComponents or shouldComponentUpdate.

References:
https://reactjs.org/docs/rendering-elements.html (Bottom):

Even though we create an element describing the whole UI tree on every tick, only the text node whose contents have changed gets updated by React DOM.

https://reactjs.org/docs/optimizing-performance.html#avoid-reconciliation

Even though React only updates the changed DOM nodes, re-rendering still takes some time. In many cases it’s not a problem, but if the slowdown is noticeable, you can speed all of this up by overriding the lifecycle function shouldComponentUpdate, which is triggered before the re-rendering process starts.
...
In most cases, instead of writing shouldComponentUpdate() by hand, you can inherit from React.PureComponent. It is equivalent to implementing shouldComponentUpdate() with a shallow comparison of current and previous props and state.

Also, read this for some more background info: https://dev.to/teo_garcia/understanding-rendering-in-react-i5i


Some more detail:
Rendering in the broader sense in React means this (simplified):

  1. Update existing component instances with the new props where feasible (this is where the key for lists is important) or create a new instance.
  2. Calling render / the function representing the component if shouldComponentUpdate returns true
  3. Syncing the changes to the real DOM

This gives you these optimization possibilities:

  1. Ensure you are reusing instances instead of creating new ones, e.g. by using a proper key when rendering lists
  2. Make sure your render method doesn't do heavy lifting or if it does, memoize those results
  3. Use PureComponents or shouldComponentUpdate to prevent the call to render altogether in scenarios where props didn't change

Answering your specific question:
To actually prevent your ShowName component from being rendered - into the Virtual DOM - if their props changed, you need to perform the following changes:

  1. Use React.memo on your ShowName component:
function ShowName({ name, onClick }) {
  return (
    <button type="button" onClick={onClick}>{name}</button>
  );
}

export default memo(ShowName);
  1. Make sure the props are actually unchanged by not passing a new callback to onClick on each render of the parent. onClick={() => onClick(index)} creates a new anonymous function every time the parent is being rendered.
    It's a bit tricky to prevent that, because you want to pass the index to this onClick function. A simple solution is to create another component that is passed the onClick with the parameter and the index and then internally uses useCallback to construct a stable callback. This only makes sense though, when rendering your ShowName is an expensive operation.

CodePudding user response:

That is happening because you are not using the key prop on <ShowName/> component. https://reactjs.org/docs/lists-and-keys.html
it could look something like this

 return (
<div>
    {array.map(name => <ShowName key={name} name={name} />)}
</div>
);
  •  Tags:  
  • Related