Understanding Custom Components in React

by iamvkr on

Introduction

React, a popular JavaScript library, simplifies this process by allowing developers to build their user interfaces using components. However, React’s approach to components is just one part of the picture. Before React, there was the concept of Custom HTML elements, and with the advent of Web Components, it became possible to extend HTML with custom tags.

Why Custom Components?

As applications grow in complexity, so does the need for more modular and reusable code. The idea behind custom components is to encapsulate functionality and styling in a way that can be easily reused across different parts of an application. This concept helps us avoid code repetition, improve maintainability, and ensure better separation of concerns.

Understanding Custom HTML Elements in Pure JavaScript

Before diving into how React works with custom components, let’s first understand how Custom Elements work in pure JavaScript. Custom Elements are part of the Web Components specification, which is a set of APIs allowing developers to define their own custom HTML tags. You can also learn more about custom components - Mdn

Example of Creating a Custom HTML Element

Let’s create a simple custom HTML element using vanilla JavaScript.

// Define a class that extends HTMLElement
class MyCustomButton extends HTMLElement {
    constructor() {
        super();
        this.attachShadow({ mode: 'open' }); // Attach a shadow DOM
        this.shadowRoot.innerHTML = `<button>Click Me</button>`; // Define the content inside the shadow DOM
    }
}

// Define the custom element
window.customElements.define('my-custom-button', MyCustomButton);

In this example:

  • MyCustomButton is a class that extends the built-in HTMLElement.
  • Inside the constructor, we attach a shadow DOM (an encapsulated part of the DOM) for better styling isolation.
  • The customElements.define() function registers the new custom element, allowing you to use <my-custom-button></my-custom-button> as an HTML tag.

How It Works

When you use the custom tag <my-custom-button></my-custom-button>, it gets replaced with the functionality defined in the JavaScript class. This works seamlessly across any modern browser that supports Web Components.

Using Custom Elements in React

Now that we understand how custom HTML elements work in vanilla JavaScript, let’s see how we can use them in a React application.

Example: Integrating Custom Elements in React

To use a custom element inside a React component, all you need to do is include the custom element tag in your JSX, just like you would with any standard HTML tag. However, React doesn’t natively support some Web Component features, so there are a few things to keep in mind.

import React from 'react';

// CustomButton component
const CustomButton = () => {
  return <my-custom-button></my-custom-button>;
};

export default CustomButton;

Important Considerations:

  1. Attributes: When passing attributes to custom elements in React, you need to use the setAttribute method, as React doesn’t automatically bind attributes to custom elements in the same way it does for standard HTML elements.
  2. Events: Custom elements in React don’t automatically trigger React’s event system. You’ll need to use standard JavaScript methods like addEventListener to listen for custom events on these elements.

Example: Listening to Custom Events

If the custom button emits a custom event, we can listen to it in React like so:

import React, { useEffect } from 'react';

const CustomButton = () => {
  useEffect(() => {
    const buttonElement = document.querySelector('my-custom-button');
    
    buttonElement.addEventListener('click', () => {
      console.log('Button clicked!');
    });

    return () => {
      buttonElement.removeEventListener('click', () => {
        console.log('Button clicked!');
      });
    };
  }, []);

  return <my-custom-button></my-custom-button>;
};

export default CustomButton;

Handling custom component in Typescript

While custom components gives a lot of flexiblity, yet it can have issues implementing in typescript.

In the following code, we get a type error as typescript doesn’t know the type of our custom button.

const App = ()=>{
    return (
        <my-custom-button></my-custom-button>
        // TS Error: Property 'my-custom-button' does not exist on type 'JSX.IntrinsicElements'
    )
}

To fix the above code, we need to declare type, after which the custom button component gets the HTMLButtonElement type:

declare module "react" {
    namespace JSX {
        interface IntrinsicElements {
            ["my-custom-button"]: DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
        }
    }
}

Now we can have all the types support, that are available in a regular button element.

Why Use Custom Components in React?

  • Encapsulation: Custom components provide a higher level of encapsulation compared to React components. With Web Components, styles and functionality are encapsulated within the custom element’s shadow DOM, preventing external styles from affecting the component and vice versa.

  • Reusability: Custom elements can be reused across different frameworks and libraries. For instance, you can use the same custom button in React, Vue, or plain HTML without needing to rewrite it.

  • Native Browser Support: Web Components are supported natively by modern browsers, meaning you don’t need to worry about bundling or additional runtime dependencies for custom elements.

  • Separation of Concerns: Custom components help separate concerns more effectively. By creating an isolated environment (using the shadow DOM), you can focus on the component’s functionality without worrying about other parts of your application.

Best Practices When Using Custom Elements in React

  • Use Shadow DOM: For better style encapsulation, always attach a shadow DOM in your custom element. This prevents styles from leaking out and affecting other parts of the application.

  • Custom Attributes: Be mindful of how attributes and properties are handled. React doesn’t automatically bind attributes for custom elements, so you will need to manually handle them.

  • Avoid Overuse: While custom elements can be powerful, React’s declarative model and component lifecycle are often simpler for most use cases. Use custom elements for interoperability or when you need strict encapsulation.

Conclusion

The integration of custom components into React offers developers the ability to create reusable, encapsulated elements that work across different frameworks. By understanding how custom elements work in pure JavaScript and how to integrate them with React, you can make better architectural decisions for your application. While React’s component model is often the best choice for most cases, there are scenarios where combining Web Components and React can provide powerful benefits.

Web Components are here to stay, and by understanding both worlds, you can build more modular, maintainable, and cross-framework compatible applications.