React: Component VS Element as Prop
Tags: webdev, react, tutorial, javascript
Published: 2022-06-27 11:49:00

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**

```tsx
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} />;
}
```
[codesandbox](https://codesandbox.io/s/passing-as-component-1-kkpq2o?file=/src/App.tsx)

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

```tsx
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>
    </>
  );
}
```

[codesandbox](https://codesandbox.io/s/passing-as-component-2-prjdjf?file=/src/App.tsx)

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

```tsx
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>
    </>
  );
}
```
[codesandbox](https://codesandbox.io/s/passing-as-component-3-qx6o4f?file=/src/App.tsx)

now open the console and increase the count, this is what you will see:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fb78vvj0kovc1xob6b2v.png)

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**

```ts
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>
    </>
  );
}
```

[codesandbox](https://codesandbox.io/s/passing-as-element-e2lyr3?file=/src/App.tsx)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sfxeef6wtg04merz0odw.png)
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](https://www.youtube.com/watch?v=VvuXkRnZi8g)