DEV Community

Acid Coder
Acid Coder

Posted on • Edited on

3

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

Heroku

Simplify your DevOps and maximize your time.

Since 2007, Heroku has been the go-to platform for developers as it monitors uptime, performance, and infrastructure concerns, allowing you to focus on writing code.

Learn More

Top comments (4)

Collapse
 
tylim88 profile image
Acid Coder • Edited
Comment hidden by post author
 
tylim88 profile image
Acid Coder • Edited
Comment hidden by post author
 
tylim88 profile image
Acid Coder • Edited
Comment hidden by post author
 
tylim88 profile image
Acid Coder • Edited
Comment hidden by post author

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

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay