DEV Community

Cover image for Crafting extensible components with React
SnoopyDev
SnoopyDev

Posted on

Crafting extensible components with React

Hello! I'm Jean. If you haven't heard of me, you're probably not alone - fame and I are like parallel lines, we never meet! I make a living as a software developer and moonlight as a tech philosopher when the mood strikes. It's been a year since my last article or in developer terms, roughly 500 cups of coffee ago. But, as they say, the code never sleeps! So I'm back with a new tutorial for you, let's take a look.

Often, we design components for a single application with well-defined behaviors, catering to specific functionalities via set props and functions. This works efficiently in many scenarios. However, consider a situation where a component is reusable across multiple front-ends, and these different interfaces sometimes require additional behaviors. What should our approach be? Should we fork the component inside each project and customize it accordingly? Although it's a possible method, it's not the most efficient, as it overlooks the inherent power of React components.

In this article, I'm going to guide you through the process of crafting extensible components, an approach that optimizes reusability without sacrificing the capacity for customization.

Consider a Blog component that includes a TitleSection, ContentSection, and CommentsSection. Each of these sections may have their unique default behavior, but there could be cases where we want to replace these with custom behavior or entirely different layouts without rewriting or altering the entire component. The concept of extensibility steps in here.

Let's first look at the structure of the Blog component.

interface Customizations {
  titleSlot?: React.ReactNode;
  contentSlot?: React.ReactNode;
  commentsSlot?: React.ReactNode;
  titleHandler?: () => void;
  contentHandler?: () => void;
  commentsHandler?: () => void;

}

interface BlogProps {
  customizations: Customizations;
}
Enter fullscreen mode Exit fullscreen mode

Here, the Customizations interface is defined to accept optional ReactNode and handler functions for each section. This interface enables passing custom React components (slots) and custom handler functions, thus providing the component with extensibility.

Let's now create the Blog component.

// Component sections
function TitleSection({ onTitleChange }) {
  return (
    <div>
      <input type="text" onChange={(event) => onTitleChange(event.target.value)} placeholder="Enter Title" />
    </div>
  );
}

function ContentSection({ onContentChange }) {
  return (
    <div>
      <textarea onChange={(event) => onContentChange(event.target.value)} placeholder="Enter Content"></textarea>
    </div>
  );
}

function CommentsSection({ onComment }) {
  return (
    <div>
      <input type="text" onChange={(event) => onComment(event.target.value)} placeholder="Enter Comment" />
    </div>
  );
}

// Blog component
function Blog({ customizations }: BlogProps) {
  const {
    titleSlot,
    contentSlot,
    commentsSlot,
    titleHandler: customTitleHandler,
    contentHandler: customContentHandler,
    commentsHandler: customCommentsHandler,
    ...restProps
  } = customizations;

  const defaultTitleHandler = (newTitle) => {
    console.log(`New title is: ${newTitle}`);
    if (customTitleHandler) {
      customTitleHandler(newTitle);
    }
  };

  const defaultContentHandler = (newContent) => {
    console.log(`New content is: ${newContent}`);
    if (customContentHandler) {
      customContentHandler(newContent);
    }
  };

  const defaultCommentsHandler = (newComment) => {
    console.log(`New comment is: ${newComment}`);
    if (customCommentsHandler) {
      customCommentsHandler(newComment);
    }
  };

  return (
    <div {...restProps}>
      {titleSlot || <TitleSection onTitleChange={defaultTitleHandler} />}
      {contentSlot || <ContentSection onContentChange={defaultContentHandler} />}
      {commentsSlot || <CommentsSection onComment={defaultCommentsHandler} />}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

In this example, when you type in the inputs of the TitleSection, ContentSection, or CommentsSection, it will trigger the respective handlers. If you pass custom handlers or custom sections to the Blog component, those will be used instead of the defaults.

Here's how to use this component with custom slots and handlers in the parent component:

import React from 'react';
import Blog, { Customizations } from 'path-to-blog-component-in-library';

function ParentComponent() {
  const customTitleHandler = (newTitle) => {
    console.log(`Custom title logic: ${newTitle}`);
  };

  const customContentHandler = (newContent) => {
    console.log(`Custom content logic: ${newContent}`);
  };

  const customCommentsHandler = (newComment) => {
    console.log(`Custom comment logic: ${newComment}`);
  };

  const customTitleSection = (
    <div>
      <h1>This is a custom title section</h1>
    </div>
  );

  const customContentSection = (
    <div>
      <p>This is a custom content section</p>
    </div>
  );

  const customCommentsSection = (
    <div>
      <p>This is a custom comments section</p>
    </div>
  );

  const customizations: Customizations = {
    titleHandler: customTitleHandler,
    contentHandler: customContentHandler,
    commentsHandler: customCommentsHandler,
    titleSlot: customTitleSection,
    contentSlot: customContentSection,
    commentsSlot: customCommentsSection,
    style: { background: 'lightgray' },
  };

  return <Blog customizations={customizations} />;
}

export default ParentComponent;
Enter fullscreen mode Exit fullscreen mode

In this example, the customHandlers will log a message with the new title, content, or comment. The customSections simply display a message indicating they are custom sections.

The custom handlers will be used instead of the default ones if provided, and the custom sections will replace the default sections. The additional props like style will also be applied to the Blog component.

And there we have it - your quick guide to creating extensible React components. I hope you found this helpful, and that you're feeling ready to bring this into your own work. It's all about adaptability and efficiency in our ever-changing tech landscape.

Remember, coding is a journey of constant learning and creativity.

Ah, don't forget to read my most loved article:
The unfair tech hiring processes

See ya!

Top comments (0)