Home > Net >  Why is this simple React component re-rendering despite none of its parameters changing?
Why is this simple React component re-rendering despite none of its parameters changing?

Time:12-14

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:

  • The user clicks the Edit button in the TableRows component
  • The isModalOpen and marginBeingEdited state variables in the parent component get updated
  • The entire ExistingMargins component re-renders
  • ...including the TableRows component, which causes lag

My 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?

CodePudding user response:

There are a couple of things going on:

  1. 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:

  2. 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:

  1. Memoize the component
  2. Create the function once and reuse it (via 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).

  • Related