-2

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?

Henny Lee
  • 2,970
  • 3
  • 20
  • 37

2 Answers2

2

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. Why? New instances always result in the old DOM node to be removed from the real DOM and a new one to be added. Even when unchanged. Reusing an instance will only update the real DOM if necessary. Please note: This has no effect on whether or not render is being called on your component.
  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.
Daniel Hilgarth
  • 171,043
  • 40
  • 335
  • 443
  • Thanks for the great read, I think you're right, I was hoping to do something with useMemo to prevent rerendering childrens unnecessarily but I think I should rethink my whole data structure. – Henny Lee Jan 05 '22 at 20:49
  • 1
    @HennyLee: I added some more details, please check – Daniel Hilgarth Jan 05 '22 at 20:57
-1

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>
);
  • Sorry I'm using a key, i just forgot to add it to the example. – Henny Lee Jan 05 '22 at 19:35
  • 1
    It makes me wonder what else is not included. could you add the whole components, parent and child? as a side note, dont use indexes as keys – Manuel Herrera Jan 05 '22 at 19:43
  • I simplified as much as possible and changed my OP. – Henny Lee Jan 05 '22 at 20:03
  • 1
    -1: This is not correct. Using key has nothing to do with rendering the component. It's just used to correspond the new props to the correct previous props to be able to correctly update `shouldComponentUpdate` and to prevent unnecessary updates of the real DOM. – Daniel Hilgarth Jan 05 '22 at 20:46
  • Um, if this is not correct you may want to issue a pr to the documentation. https://reactjs.org/docs/lists-and-keys.html https://reactjs.org/docs/reconciliation.html#recursing-on-children @DanielHilgarth – Manuel Herrera Jan 05 '22 at 21:28
  • My answer was to the OP, which did not include the context just the return statement of the parent component. That's why i asked for the parent and child code which is what enabled you to answer the question. May I add that your answer includes what you said to be wrong "Ensure you are reusing instances instead of creating new ones, e.g. by using a proper key when rendering lists" @DanielHilgarth – Manuel Herrera Jan 05 '22 at 21:43
  • @ManuelHerrera: You also have a misunderstanding in the concepts. "reconciliation" - where you link to in your comment - is the process of syncing the virtual DOM to the real DOM. That's where the key helps. See also my answer. The key does NOT prevent `render` to be called, which is what the OP was wondering about. – Daniel Hilgarth Jan 06 '22 at 07:16
  • @ManuelHerrera: I've tried to clarify my answer a bit, see the latest edit. – Daniel Hilgarth Jan 06 '22 at 07:18