Most performant way to declare components in React

Viewed 54

I lack an understanding of how the React framework works under the hood, and could use some clarification. Particularly how React optimizes the update process of the DOM (by determining which part of the DOM has actually changed between renders).

For example, what is the difference between implementation A and B?

Implementation A:

// ParentComponent.js
function ParentComponent(props) {
    
    const [clicks, setClicks] = useState(0);
    
    const ChildComponent = (props) => {
        return (
            <div>
                child component
            </div>
        )
    }

    return (
        <div>
            <button onClick={() => setClicks(clicks + 1)}>Click Me</button>
            <ChildComponent />
        </div>
    )
}

Implementation B:


// ChildComponent.js
const ChildComponent = (props) => {
    return (
        <div>
            child component
        </div>
    )
}

// ParentComponent.js
import ChildComponent from './ChildComponent.js'

function ParentComponent(props) {

    const [clicks, setClicks] = useState(0);

    return (
        <div>
            <button onClick={() => setClicks(clicks + 1)}>Click Me</button>
            <ChildComponent />
        </div>
    )
}

The way I see it, every time implementation A re-renders the <ParentComponent> (via a state change) it will redeclare the function which returns JSX for the <ChildComponent/>, meaning even though nothing ever changes to the <ChildComponent> between renders, React would still re-render that component, no?

On the other hand, I don't really know how implementation B is any different

2 Answers

There are many misconceptions about re-rendering components in React, as can be seen from existing comments

The only reason to not declare one component (ChildComponent in your Implementation A) inside of another component (ParentComponent in Implementation A) is the fact that, React components are functions, and each time ParentComponent is re-rendered, Javascript will naturally create a new ChildComponent function. Even though it has the same name to you, to React, it is a new function (a new type of component, not related to previous ChildComponent), so it will destroy previously rendered instance of ChildComponent and render a new instance of ChildComponent. React will simply compare references to a ChildComponent function (using === operator), and, since those functions will differ every render of the parent, React will consider them a new component each time. Again, what you end up by nesting is NOT just a re-render of a child (when existing instance is re-rendered with possibly updated props/state or not), but a teardown of existing component and mounting of a new component in its place. That means that, if your ChildComponent had any state (useState hook, or DOM state, like cursor focus position in an input tag), it would lose it, because it gets destroyed and fresh component is mounted in its place

You want to read about reconciliation in React docs which explains how React decides to keep or tear down existing elements, it's quite short and simple. There are two types of component trees you might be thinking of - tree of React elements (virtual tree of your "live" component instances) and DOM tree (html components rendered by the browser on the page). Re-rendering components with the same props merely means running updates on the virtual tree of React elements. It's not an expensive operation. It doesn't redraw actual components in the DOM tree unless it's necessary. In React it is perfectly fine to re-render your components, on a contrary to what comments here suggest. In fact, with functional components, re-rendering parent (changing its props or state) already triggers re-render on all of its descendant children. It's not expensive unless those children need to update their HTML, and since only parent changed they will not need to do so (and if they do need to update their HTML, they better do so, otherwise your app doesn't work)

With that being said, any claims like "you should not declare one component inside the other because they will both re-render" are false, as both components will re-render either way, nested or not

Situations in which you want to reuse some render logic (what you are trying to achieve with declaring one component inside of another) happen all the time. It's analogous of declaring local javascript function inside of another function, as opposed to declaring all functions in the upper scope. Local functions are perfectly fine and encouraged. Back to React, to solve your problem, you just need to create a function to render react elements, instead of a component:

as we have settled above, this is wrong:

function Parent() {
  
  function Child(props) {
    return <p>this is a child: {props.name}</p>
  }
  
  return (
    <div>
      <Child name={'child1'}></Child>
      <Child name={'child2'}></Child>
    </div>
  )
}

this is correct:

function Parent() {
  
  function renderChild(name) {
    return <p>this is a child: {name}</p>
  }
  
  return (
    <div>
      { renderChild('child1') }
      { renderChild('child2') }
    </div>
  )
}

Again, in the first example, Child is a Javascript function that is treated as a React component, and, since this function is re-created every render of a Parent, React will do comparisons between old Child and new Child (which will show that they are not equal) and will decide to destroy previous instance of Child every time and create new one in its place. In the second example, renderChild is not a React component (it's lowercased and not called as a JSX component, with angle brackets) therefore it doesn't have its state state nor a place in virtual React tree, so React will not even attempt to make a comparison

Declaring a component inside of another component will result in re-rendering both components whenever a state change occurs.

Inefficiencies aside, there are other practical reasons not do this. Separating concerns with separate components is more readable and clear. It also allows for more reusability.

For example, if you are rendering a list of movies you might have a MovieList component, and a Movie component. If you nest the Movie component in MovieList, you cannot use Movie anywhere else.

In short, separate your components like you separate your concerns. This will give you more readable, reusable and efficient code.

Related