Reputation: 3685
I have a fairly simple React component which renders two components within it: one is a big list and another is a modal. When a button within the list is clicked, the modal appears. This process is causing the list component to re-render, thus inducing the lag. There is a gap in my React knowledge as to why this is happening.
Here's the code:
const ExistingMargins = ({ margins }) => {
const [isModalOpen, setIsModalOpen] = useState(false);
const [marginBeingEdited, setMarginBeingEdited] = useState();
return (
<>
<TableRows
margins={margins}
onEditButtonClicked={(marginToEdit) => {
setMarginBeingEdited(marginToEdit);
setIsModalOpen(true);
}}
/>
<EditModal
isModalOpen={isModalOpen}
closeModal={() => setIsModalOpen(false)}
margin={marginBeingEdited}
/>
</>
);
};
const TableRows = ({ margins, onEditButtonClicked }) => {
return (
<table>
{margins.map(margin => (
<tr key={margin.Id}>
<td>{margin.Name}</td>
<td>
<button onClick={() => onEditButtonClicked(margin)}>Edit</button>
</td>
</tr>
))}
</table>
);
};
My understanding: is that React applies a unique key to the two components being rendered here (TableRows
and EditModal
) and that, whenever any state variable changes, the component re-renders and updates the shadow-DOM. React then compares the new and previous shadow-DOM to detect changes. Any changes get rendered in the actual-DOM. Since nothing in TableRows
is changing, no laggy updates to the actual-DOM happens.
This isn't the case. Here's what is actually happening:
TableRows
componentisModalOpen
and marginBeingEdited
state variables in the parent component get updatedExistingMargins
component re-rendersTableRows
component, which causes lagMy understanding is that TableRows
shouldn't re-render on the actual-DOM because no state variables being passed to it have changed.
This is not happening. I can see in Chrome's React Profiler that there's a big, slow re-render of the table rows.
Why is this happening? Memoisation might fix it, but where is my understanding incorrect?
Upvotes: 0
Views: 290
Reputation: 1075735
There are a couple of things going on:
TableRows
isn't memo-ized (via React.memo
for instance, since it's a function component; for a class component you'd inherit from React.PureComponent
). Non-memoized components re-render if their parent re-renders, even if their props don't change.
But also:
Its props are changing, because of this code:
onEditButtonClicked={(marginToEdit) => {
setMarginBeingEdited(marginToEdit);
setIsModalOpen(true);
}}
That creates a new function every time the parent is rendered, so TableRows
sees a changed prop.
To solve that:
useCallback
, useMemo
, or useRef
)See my answer to Should we use useCallback in every function handler in React Functional Components? for more details.
Side note re:
My understanding is that TableRows shouldn't re-render on the actual-DOM because no state variables being passed to it have changed.
I think the term you're looking for there is "commit," not render. When React renders your component, your component function is called and returns an element tree. React then commits that tree to the DOM (doing a diff and only making the necessary changes).
Upvotes: 1