2

I have a component that creates several components using a loop, but I need to rerender only the instance being modified, not the rest. This is my approach:

    function renderName(item) {
       return (
          <TextField value={item.value || ''} onChange={edit(item.id)} />
       );
    }
    
    function renderAllNames(items) {
       const renderedItems = [];
       items.forEach(x => {
          const item = React.useMemo(() => renderName(x), [x]);
          renderedItems.push(item);
       });
       return renderedItems;
    };
    
    return (
      <>
         {'Items'}
         {renderAllNames(names)};
      </>
    );

This yells me that there are more hooks calls than in the previous render. Tried this instead:

function renderAllNames(items) {
           const renderedItems = [];
           items.forEach(x => {
              const item = React.memo(renderName(x), (prev, next) => (prev.x === next.x));
              renderedItems.push(item);
           });
           return renderedItems;
        };

Didn't work either... the basic approach works fine

function renderAllNames(items) {
           const renderedItems = [];
           items.forEach(x => {
              renderedItems.push(renderName(x));
           });
           return renderedItems;
        };

But it renders all the dynamic component everytime I edit any of the fields, so how can I get this memoized in order to rerender only the item being edited?

1
  • Could you provide the full code for component? Commented Feb 27, 2021 at 23:55

1 Answer 1

2

You're breaking the rules of hooks. Hooks should only be used in the top level of a component so that React can guarantee call order. Component memoisation should also really only be done using React.memo, and components should only be declared in the global scope, not inside other components.

We could turn renderName into its own component, RenderName:

function RenderName({item, edit}) {
    return (
      <TextField value={item.value || ''} onChange={() => edit(item.id)} />
   );
}

And memoise it like this:

const MemoRenderName = React.memo(RenderName, (prev, next) => {
  const idEqual = prev.item.id === next.item.id;
  const valEqual = prev.item.value === next.item.value;
  const editEqual = prev.edit === next.edit;

  return idEqual && valEqual && editEqual;
});

React.memo performs strict comparison on all the props by default. Since item is an object and no two objects are strictly equal, the properties must be deeply compared. A side note: this is only going to work if edit is a referentially stable function. You haven't shown it but it would have to be wrapped in a memoisation hook of its own such as useCallback or lifted out of the render cycle entirely.

Now back in the parent component you can map names directly:

return (
  <>
     {'Items'}
     {names.map(name => <MemoRenderName item={name} edit={edit}/>)}
  </>
);
Sign up to request clarification or add additional context in comments.

2 Comments

Hey, I would like to add a comment here, please excuse-me :) The edit prop which is an anonymous function may not get through your equality check. So you should memoise that using useCallback, or maybe make it known by naming thje function or setting it to a variable. In your custom equality function for the memo, you could pass a plain isEqual function from Lodash. Also may improve readability, in my op.
@CaioKoiti My answer assumes that edit is the named function from OPs post. Theres no reason to think it is anonymous here, and I did say exactly that about useCallback in my answer. I disagree about using a deep equality checker here. You should be careful about using a more expensive algorithm when trying to improve performance. A better idea would be to break all the values out of the object into their own props, so you wouldn't need a custom check at all. I didn't do this here as the point of the post is to elucidate how React.memo works.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.