DEV Community

Acid Coder
Acid Coder

Posted on • Edited on

React: Component VS Element as Prop

In this article we will look into how to create a component that accepts another component/element as prop effectively.

Why do we need it?
We already have children prop, so why do we need this?

Imagine we want to create a button component, normally the child is text. And now we need another child for icon, this is where component/element as prop is useful, it allows us to have a child in separated parents

There are 2 ways, passing as component and passing as element.

Terminology:
1) component: function that return a JSX element
2) element: JSX element

We will have a clear answer on which pattern we should use in the end.

1) Passing as component

const A = ({ component }: { component: () => JSX.Element }) => {
  const Component = component;
  return <Component />;
};

const B = () => {
  return <div>as component</div>;
};

export default function App() {
  return <A component={B} />;
}
Enter fullscreen mode Exit fullscreen mode

codesandbox

so far so good, but what if you need to pass some state to B?

import { useState } from "react";

const A = ({ component }: { component: () => JSX.Element }) => {
  const Component = component;
  return <Component />;
};

const B = ({ count }: { count: number }) => {
  return <div>{`as component with state:${count}`}</div>;
};

export default function App() {
  const [count, setCount] = useState(0);
  return (
    <>
      <A component={() => <B count={count} />} />
      <button
        onClick={() => {
          setCount((count) => count + 1);
        }}
      >
        increase the count
      </button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

codesandbox

that seems to do it, it is working.

however please take a closer look at () => <B count={count} />, something is not right, can you guess what is it?

now try the same code again, but this time we add useEffect to B to track the mount and unmount event

import { useEffect, useState } from "react";

const A = ({ component }: { component: () => JSX.Element }) => {
  const Component = component;
  return <Component />;
};

const B = ({ count }: { count: number }) => {
  useEffect(() => {
    console.log("mounted");
    return () => {
      console.log("unmounted");
    };
  }, []);
  return <div>{`as component with state:${count}`}</div>;
};

export default function App() {
  const [count, setCount] = useState(0);
  return (
    <>
      <A component={() => <B count={count} />} />
      <button
        onClick={() => {
          setCount((count) => count + 1);
        }}
      >
        increase the count
      </button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

codesandbox

now open the console and increase the count, this is what you will see:
Image description

this is because for every rerender(function rerun), () => <B count={count} /> return a new reference because it is a anonymous function, so React think it is a different component and remount it.

can we solve this? The solution is simple, just pass it as element instead

2) Passing as Element

import { useEffect, useState } from "react";

const A = ({ element }: { element: JSX.Element }) => {
  return element;
};

const B = ({ count }: { count: number }) => {
  useEffect(() => {
    console.log("mounted");
    return () => {
      console.log("unmounted");
    };
  }, []);
  return <div>{`as component with state:${count}`}</div>;
};

export default function App() {
  const [count, setCount] = useState(0);
  return (
    <>
      <A element={<B count={count} />} />
      <button
        onClick={() => {
          setCount((count) => count + 1);
        }}
      >
        increase the count
      </button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

codesandbox

Image description
as you can see, the component is only mounted twice**

That is all, passing as element is the pattern we should use.

**React 18 mount event run twice in dev environment, source

Top comments (4)

Collapse
 
tylim88 profile image
Info Comment hidden by post author - thread only accessible via permalink
Acid Coder • Edited

Normally, the child is ReactNode.

yes it is, but what I mean is normally people use the child for the text element (see more explanation in below)

Not really, you can have several elements as children, so you really don't need another property for that. Something like:

Your code could look something like this, using children:

No, you missed the point

I mean, of course, you can do that, that is because I use a simple example, but this is not the purpose I created this post

now imagine you have an App component, it has 3 sections, header, content, and footer

now imagine I already defined the body, I want another dev to define the header and footer, so obviously you cannot do this in one child (or one big child) because the header and footer should not be in the same component

This is when an element as a prop comes to play, simply create a header and footer props and let another dev define them.

Technically you can add a lot of CSS to achieve the same effect, but that is not really a good way to do things.

Again this is just an example and probably not a good one because practically speaking nobody defines the body before the header

But what am I saying is the one one big children is not useful if the component has proper layout for their children, another simple example is left icon, right icon, and loader for button.

Normally the component creator already positioned those element well, you just need to fill the element prop without worrying about the position and also the conditional logic for the loader.

many UI libraries use this kind of pattern for button, example is material ui, ant design, mantine and etc etc: child for text, element props for left, right icon and loader.

This pattern is very useful, convenient and common

again, I am not specifically talking about button and icon, I am talking about component vs element as prop in general, and why people choose element prop over component prop

Yup, it does that so you use useEffect as little as possible

it is not

React is unopinionated, it doesn't care how user practise it, the reason is something else

 
tylim88 profile image
Info Comment hidden by post author - thread only accessible via permalink
Acid Coder • Edited

children here is normally a text refer to libraries like material UI

did you even bother to click the links I provided?

material ui
button

ant design
button2

mantine
button3

I am talking about usage, normally people use it as button text, just like the plain <button>text</button>!

those child become button text!

I said normally, not only, I am not saying it accepts only string!

so yeah, you misunderstood

and I perfectly aware that the child can accept any react element, you could even try to fit the whole app in the button child

for god sake, people are not stupid

please understand the context

children allows more than 1 child on it, so that isn't a valid point either.

what I mean here is you you need to wrap them in one big child(eg in a fragment), except array, so yeah I agree it has some inaccuracy there

Think it this way: If I write a post about how to wash a Dog, but I started the post saying something like "Dogs are the dirtiest animals in the world, so here are a few ways to wash them", folks in the comments discussing that "dogs aren't the dirtiest animals in the world" would be right to do so, right? The same applies here 😄

please no strawman argument here, thanks, that is not totally what I mean

stop argument like this, because it is very difficult to explain to you what is wrong with your logic

This was derailed enough, for next time remember that folks will comment about the content of your entire post, no just the title or the final remarks, so if you don't want to talk about something in particular, omit it from the post.

You are expecting people to express themselves in 100% accuracy, this is unrealistic, even I cant express myself with 100% accuracy in my native tongue

even with 100% accuracy, the receiver may still understand it in some other way

write cannot express themself with 100% accuracy, reader cannot understand it with 100% accuracy, it is just normal because we as human are not perfect.

I understand that people may not get the point when they first read the post, which is why I try to clarify what this post is about in my first reply to you, but you ignore it

if you ignore it, then that is your problem, not mine

inaccuracy in communication is something that we need to tolerate, hence understanding the whole context is important, not paying excessive attention to wording!

if you insist to talk pedantically, then you are looking for troubles

next time remember to understand the context when you read the post!

 
tylim88 profile image
Info Comment hidden by post author - thread only accessible via permalink
Acid Coder • Edited

no, you still don't understand what is the purpose of this post

and as I said my example is just an example to explain concept, not really to build some component

example that you are showing is working and nothing is wrong with it, but that is because it is simple

in a complex UI, thing is much more complicated than any of the example shown in here, the creator already did the layout for us, the position of the internal element, responsiveness, flex etc etc and also deal with conditional logic for stuff like loading

an example of a complex AppShell , it simply make more sense to use element prop than just using child here

even simple components also benefit from this, examples are the buttons

and there are two ways to achieve this, component vs element, and the purpose of this post is to demonstrate why element is better than component, due to the remount issue (this issue is quite well known in the old version of React Router, where RR provided all three options, child, element and component as prop)

which is why majority of the UI libraries use element as prop pattern

 
tylim88 profile image
Info Comment hidden by post author - thread only accessible via permalink
Acid Coder • Edited

You said that you understand how a ReactNode works, but you also said:

I did not claim that I understand ReactNode, I simply telling you what I understood, I accept that my understanding may be flawed, you are free to point it out and I thank you for that example, I learned something

"strawman argument"

that was indeed a strawman argument, I simply point it out what is wrong with your argument, just like you trying to point out what is wrong with mine

"The initial statements aren't correct (and I expanded on the reasons), and if you're sure they are correct, then they weren't clear or were unrelated to the topic at hand".

the children part you are right, but you totally misinterpreted normally the child is text.

No need for Fragments or arrays. If a component only accepts a single child, that's a component with a poor API, but that doesn't mean is ok to do it like that. I'm aware that some popular component libraries do it that way, but being popular doesn't imply is good either.

it is impossible to accept a component only as a single child, as you already corrected, hence that's a component with a poor API is not an existing issue

component that use element prop is also capable to accepts multiple child

I'm aware that some popular component libraries do it that way, but being popular doesn't imply is good either.

doesn't imply it is bad either, but since majority component libraries using such pattern and you did not provide a good counter point against complex component, internal layout and logic handling, it is just normal for me to favor such approach, especially if I want try to create a ui libraries or a complex component

basically those authors approach are more credible than yours

My primary language is Spanish and English is my second.

English is my fourth, but I couldnt care less, nobody can talk and understand with 100% accuracy, no matter his 1st, 2nd and 3rd, that is the point

I'm open to keep debating this in a friendly manner if you want to. Some folks in our community assume people wants to fight as soon as a debate is started, or that debates are things to be "won",

stop implying I am such person, I am pissed because you are trying to blame me on why you derailed with flawed analogy

Some comments have been hidden by the post's author - find out more