Composing React Components with Slots similar to vueJs

Sometimes composability in reactJs seems difficult And All kind of comoposability could not be solved with children only. And we feel ReactJs component creation sometimes added a lot of props for only conditionally render Components inside main component. we find some missing features in ReactJs And these features are available in other framework/library.

Similar one feature is available in the VueJs that makes vuejs Components more composable  very easily. That feature is the slot.

Let’s check an example where we miss the slots in ReactJs. Let’s say we have one Product component and this product component has a lot of features and different feature required on different places in the application.

This kind of problem we generally solve with conditional rendering and end up with a lot of components inside our generic component. We need to have some way to render specific components from specific places.

So we will try to create the slot feature in the ReactJs. We need to iterate over the component’s children to extract out the specific part of the component which we will pass inside the JSX with the slot name.

Before going into any implementation details, One thing we need to keep in the mind that children in the ReactJs has all the methods/functions available that are available for JavaScript Array.

So we can map over this children or any other function we want to call over it, we can do so.

Let’s create one small function to extract out the slot with the given name.

function getElementBySlot(children, slotName) {
  return (
    children.find((el) => {
      return el?.props?.slotName === slotName;
    }) || null
  );
}

This simple function is too powerful that can extract out the Component with that passed prop slot name.

Now let’s create a product component and we will make that product component composable.

function Product() {

  return (
    <div className="ProductWrapper">
      <div>{'header'}</div>
      <div>
        Common Product Body section
        <div>
          <button>Add to Cart</button>
          <button>Add to Wishlist</button>
        </div>
      </div>
      <div>{'footer'}</div>
      <div>Some other View </div>
    </div>
  );
}

In this Product component, we have some of the middle part that is common throughout the application and only dependent on the props value but two parts of the component will be replaced by the complete components “header” and “footer” and these part will not be replaced by children. Only one of the part could be replaced by children.

So in order to solve this issue we need to use the slot feature here.

Check another version of the same component with slots implemented.

 

const HeaderSlot1 = () => {
  return <div>header slot placer1</div>;
};
const HeaderSlot2 = () => {
  return <div>header slot placer2</div>;
};
const FooterSlot1 = () => "FooterSlot1";
const FooterSlot2 = () => "FooterSlot2";
const IconViewSlot = "iconView";

export default function App() {
  return (
    <div className="App">
      <h1>Product Variants</h1>
      <Product>
        <Header value={123} slotName="header">
          <HeaderSlot1 />
          Testing1 Content
          <span slot={IconViewSlot}>span Icon1</span>
        </Header>
        <Footer name={4567} slotName="footer">
          <FooterSlot1 />
        </Footer>
      </Product>

      <Product>
        <Header value={890} slotName="header">
          <HeaderSlot2 />
          Testing2 Content
          <span slot={IconViewSlot}>span Icon2</span>
        </Header>
        <Footer name={99999} slotName="footer">
          <FooterSlot2 />
        </Footer>
      </Product>
    </div>
  );
}

function Product({ children }) {
  const header = getElementBySlot(children, "header");
  const footer = getElementBySlot(children, "footer");

  return (
    <div className="ProductWrapper">
      <div>{header}</div>
      <div>
        Common Product Body section
        <div>
          <button>Add to Cart</button>
          <button>Add to Wishlist</button>
        </div>
      </div>
      <div>{footer}</div>
      <div>Some other View </div>
    </div>
  );
}

function Header({ value, children }) {
  //const iconView = getElementBySlot(children, IconViewSlot);
  return (
    <div>
      {/* <span>{iconView}</span> */}
      Header {value}
      <div>{children}</div>
    </div>
  );
}

function Footer({ name, children }) {
  return (
    <div>
      Footer {name}
      {children}
    </div>
  );
}

function getElementBySlot(children, slotName) {
  return (
    children.find((el) => {
      return el?.props?.slotName === slotName;
    }) || null
  );
}

Here we just need to provide the slotName and that will be dynamically placed on its place using the function we have created. And another more important part is code is more readable.

Working example is here

Leave a comment

Your email address will not be published. Required fields are marked *