'React: jsx in a variable vs a function vs a separate component

For rendering smaller components/jsx within a bigger component, there are multiple approaches that one can follow. For example, consider this:

Method 1:

function BigComponent(props) {
  const renderSmallComponent1 = () => <div>{props.a}</div>;
  const renderSmallComponent2 = () => <div>{props.b}</div>;

  return (
    <div>
      {renderSmallComponent1()}
      {renderSmallComponent2()}
    </div>
  )
}

Method 2:

function BigComponent(props) {
  const smallComponent1 = <div>{props.a}</div>;
  const smallComponent2 = <div>{props.b}</div>;

  return (
    <div>
      {smallComponent1}
      {smallComponent2}
    </div>
  )
}

Method 3:

function SmallComponent1({ a }) {
  return <div>{a}</div>;
}

function SmallComponent2({ b }) {
  return <div>{b}</div>;
}

function BigComponent(props) {
  return (
    <div>
      <SmallComponent1 a={props.a} />
      <SmallComponent2 b={props.b} />
    </div>
  )
}

I am just trying to understand the difference in these 3 in terms of

  • dev experience,
  • how the framework treats them,
  • are there any performance optimizations,
  • are there differences in runtime behaviours in all of these?
  • Is either one better to use in certain scenarios?

These are the things that I understand:

  • in Method 3, all SmallComponent are React components which are rendered in another component, so they would have a component lifecycle, while in method 1 and 2, they are simple jsx, which does not have lifecycle, so they would not be mounted / unmounted as React components
  • in Method 2, we would be eagerly evaluating the JSX as it is directly a variable, while in method 1, it would only be evaluated when the function is called in render. So, in case, we have any conditional rendering, the eager evaluation might just be wasteful.

A few other helpful articles:

UPDATE: it seems observation 1 is incorrect as all 3 of them would still be rendered as react components, and hence would have a component lifecycle. So react would mount/unmount them.

UPDATE 2: No, observation 1 is correct, method 1 and 2 are both treated as regular jsx as part of the BigComponent and they are not treated as react component which have a lifecycle.

UPDATE 3: There is another method Method 4:

function BigComponent(props) {
  const SmallComponent1 = () => {
  return <div>{props.a}</div>;
  }
  const SmallComponent2 = () => {
  return <div>{props.b}</div>;
  }

  return (
    <div>
      <SmallComponent1 />
      <SmallComponent2 />
    </div>
  )
}

this is similar to Method 3, but Method 3 vs Method 4 is slightly different in execution, when debugging through dev tools.



Solution 1:[1]

Method 2:

function BigComponent(props) {
  const smallComponent1 = <div>{props.a}</div>;
  const smallComponent2 = <div>{props.b}</div>;

  return (
    <div>
      {smallComponent1}
      {smallComponent2}
    </div>
  )
}
  • If you want to a large UI into seperate smaller UI, this method will give you best performance because
    • It is still just one big UI component.
    • react just have to solve variable references.
    • while re-rendering, BigComponent,smallComponent1 and smallComponent2 are rendered together as single unit.
    • smallComponent1 and smallComponent2 cannot have their own state, life cycles and hooks.
    • smallComponent1 and 2 needs to be re-initialized everytime Bigcomponent state is changed. So it is good practise to wrap them with useMemo() if the result of those smallComponents are coming from an expensive computation.

Method 3:

function SmallComponent1({ a }) {
  return <div>{a}</div>;
}

function SmallComponent2({ b }) {
  return <div>{b}</div>;
}

function BigComponent(props) {
  return (
    <div>
      <SmallComponent1 a={props.a} />
      <SmallComponent2 b={props.b} />
    </div>
  )
}
  • React needs to resolve reference as well as execute the function after resolving the reference.

  • It is a composition of react's actual child components into a large Component.

  • Child components are allowed to have their own hooks.

  • Child components are not re-initialized but are re-rendered if BigComponent state is changed.

  • There is chance of SmallComponent1 and SmallComponent2 getting re-rendered multiple times on BigComponents rendering once if small components are updating thier own state based on props change in parents.

  • if each SmallComponents are supposed to use multiple props which state of BigComponents, Keeping SmallComponents outside BigComponent does offer good developer experience.

  • I hope Method 1 and Method 4 can also be understood using these above points.

  • Note: childcomponents stored in variable and childcompoents as function becomes tricker if your application logic is using ref or DOM element for maininting focus or anchor point of rendering.

Solution 2:[2]

Have you taken a look at the compiled JS in a React project?

JSX tags are essentially transformed in to React.createElement statements. You can read the docs here. Essentially the syntax is:

React.createElement(FunctionOrClassComponent, { props }, ...children)

In all three of your examples this would take place. In all three examples, the smaller components are functional components rather than class components. That is to say, they don't have the React lifecycle methods of a class component, but they can use equivalent React hooks - should you want to.

Edited: Evaluation (instantiation and rendering) depends on your render logic. If you have conditional rendering statements or your functions return null (or less content) based on certain conditions, then obviously you're doing less work. And as you rightly pointed out in the comments below, when you assign a JSX.Element to a variable, that is evaluated inline rather than as a result of a function - so that happens immediately.

To me, all three are valid approaches. To address your questions:

  • dev experience,
    • for small components with minimal state, functional components as variables or lambdas are convenient to write and easily read/parsed when revisiting code at a later date. When a component becomes more complex, you may have to reconsider how it's written and perhaps use Class components.
  • how the framework treats them,
    • to my knowledge the framework treats all three of your examples the same in terms of compilation. I'm unsure about rendering optimisation.
  • are there any performance optimizations,
    • your examples don't depict anything computationally onerous so performance optimization options are not so obvious
  • are there differences in runtime behaviours in all of these?
    • they are all translated to React elements, monitored for props changes, and re-rendered if parents re-render (if things like React.memo are not employed) -- there may be differences vs class-based elements, but I would guess that the runtime differences between your three examples are minimal
  • Is either one better to use in certain scenarios?
    • The differences between all three are more a matter of standards or etiquette than functional outcome. As a developer, I would be able to read and understand all three, but working in a team - I would want to see a standard approach.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2