35

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.

gaurav5430
  • 12,934
  • 6
  • 54
  • 111
  • You seem to understand the cases quite well. I'm not sure what particular aspect you wanna dig deeper? – hackape Apr 21 '21 at 01:49
  • Questions like: is there a scenario where one of these methods would work differently from others, or not work at all? Can they always replace each other? – gaurav5430 Apr 21 '21 at 02:46
  • Hmm, it still sounds very much an open question to me. I don’t really know where to start cus I can construct all sort of examples to illustrate diff behaviors. – hackape Apr 21 '21 at 03:48
  • @hackape i think different sort of examples would still be helpful and might answer the major parts of the question, which is essentially about the differences in these 3 approaches – gaurav5430 Apr 21 '21 at 03:51
  • Instead of scrutinizing special usage cases, I think the better approach would be actually learn the internal work of react. Once you learn the “physics” you know how to do all the “engineering”. – hackape Apr 21 '21 at 03:51
  • I can say this. #1 and #2 are equivalent, can be viewed as one. #3 is different. – hackape Apr 21 '21 at 03:53
  • That is to say, the most meaningful question is this one: “how the framework treats them”. I’ll find time to write one. – hackape Apr 21 '21 at 03:55
  • Strongly recommend [this gist](https://gist.github.com/jasper-lyons/f36c7c2b093fe072edfaf59db95aff2c) React in 100 lines – hackape Apr 21 '21 at 04:01
  • Method 4 is a definitely an anti-pattern. Every time BigComponent is re-rendered, SmallComponent1 and SmallComponent2 are re-declared. React doesn't see these as the same components that were there last render, so it handles this by unmounting the old small components and mounting new ones, instead of simply re-rendering them. This is bad for performance, but worse it actually forces the section of the DOM associated to SmallComponent1 to be re-written, potentially causing a flicker. You can mitigate this a little with correct usage of `useMemo`, but in the best case Method 3 is just simpler. – Chrispher Aug 23 '23 at 18:22

2 Answers2

7

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.

Firoj Siddiki
  • 1,649
  • 1
  • 20
  • 22
  • I am wondering will there be any difference between method 2 and directly inline all the jsx inside the return in terms of performance and rerendering? `
    {props.a}
    {props.b}
    `
    – Boom PT Jun 09 '23 at 07:31
  • If you are not separating into variables, you will have minute performance gain because resolving variables is not required. – Firoj Siddiki Jun 10 '23 at 09:44
3

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.
t0mgerman
  • 186
  • 2
  • 8
  • Regarding eager evaluation: method 1 would only evaluate things when the function is called inside render/return, but method 2 would evaluate things even before that as it is a variable – gaurav5430 Apr 21 '21 at 03:11
  • Regarding using hooks: in method 1 and 2, i think even if we were able to use hooks, they would belong to the parent, and not these small components. – gaurav5430 Apr 21 '21 at 03:16
  • Apologies, it's late here - I confused what you were saying about evaluation with eager loading (as opposed to lazy loading). You are correct, method 2 would evaluate things inline as those are not functions but JSX element declarations (as variables). For method 1 and 3 if you used hooks in the small components and you declared and assigned the hook outputs to block scoped variables (using let or const) they would belong to the small components... they would only hoist up to the parent component if you used var. – t0mgerman Apr 21 '21 at 03:22
  • Sorry made a mistake there too... I meant to say they would only belong to the parent component if you declared them there. – t0mgerman Apr 21 '21 at 03:32