Frontend Development 15 min read

Integrating Web Components into React Applications with a Custom Hook

This tutorial demonstrates how to use a previously built Web Components dropdown inside a React app, covering the creation of an abstraction layer, proper prop handling, event registration, and a reusable custom Hook that formats data and manages listeners.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Integrating Web Components into React Applications with a Custom Hook

From React Component to Web Components

To use a third‑party Web Component (the dropdown) in a React component, you can import it and render it directly, but a better practice is to wrap it in an abstraction layer to keep the system decoupled.

npm install [email protected]
import React from 'react';
import 'baixiaobai-web-components-dropdown';

const BusinessComponent = props => {
  return (
);
};

Abstraction Layer

Instead of using the Web Component directly in business code, create a wrapper component that isolates changes and allows independent evolution of both sides.

import React from 'react';
import 'baixiaobai-web-components-dropdown';

const WebDropdown = props => {
  return (
);
};

const BusinessComponent = () => {
  return (
);
};

The abstraction layer decouples the two systems, isolates changes, and enables seamless integration of external systems.

Props

Pass primitive props (string, number) directly, but objects, arrays, and functions need special handling. Objects and arrays must be JSON‑stringified before being set as attributes.

const props = {
  label: '下拉菜单组件',
  option: 1,
  options: [
    { "label": "黄金糕", "value": 1 },
    { "label": "狮子头", "value": 2 },
    { "label": "螺蛳粉", "value": 3 },
    { "label": "双皮奶", "value": 4 },
    { "label": "蚵仔煎", "value": 5 }
  ]
};

const WebDropdown = ({ label, option }) => {
  return (
);
};

const WebDropdown = ({ label, option, options }) => {
  return (
);
};

If the underlying component already parses JSON (as shown in its getter/setter), you can pass the object directly.

get options() {
  return JSON.parse(this.getAttribute('options'));
}

set options(value) {
  this.setAttribute('options', JSON.stringify(value));
}

Events

Custom events cannot be passed as attributes; you must register an event listener on the custom element. Example using onOptionChange :

React.useEffect(() => {
  document
    .querySelector('baixiaobai-web-components-dropdown')
    .addEventListener('onOptionChange', event => {
      console.log(event.detail);
    });
}, []);

A more robust approach uses a ref and registers the listener inside the wrapper component:

const WebDropdown = ({ label, option, options }) => {
  const ref = React.useRef();
  React.useEffect(() => {
    const { current } = ref;
    current.addEventListener('onOptionChange', event => {
      console.log(event.detail);
    });
  }, [ref]);

  return (
);
};

Custom Hook: useWebComponentsCustomElement

The hook abstracts prop conversion and event registration. It JSON‑stringifies objects/arrays, passes primitives unchanged, maps function props to event listeners, and returns the processed props together with a ref.

import React from 'react';
import 'baixiaobai-web-components-dropdown';
import useWebComponentsCustomElement from './useWebComponentsCustomElement';

const WebDropdown = props => {
  const [elementProps, ref] = useWebComponentsCustomElement(props);
  return (
);
};

Property Handling

function getType(obj) {
  return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1');
}
function isFunctionType(obj) { return getType(obj) === 'Function'; }
function isObjectOrArrayType(obj) { return ['Object', 'Array'].includes(getType(obj)); }

const elementProps = Object.keys(props)
  .filter(key => !isFunctionType(props[key]))
  .reduce((acc, key) => {
    const prop = props[key];
    const computedKey = propsMapping[key] || key;
    if (isObjectOrArrayType(prop)) {
      return { ...acc, [computedKey]: JSON.stringify(prop) };
    }
    return { ...acc, [computedKey]: prop };
  }, {});

Event Handling

React.useEffect(() => {
  const { current } = ref;
  let fns;
  if (current) {
    fns = Object.keys(props)
      .filter(key => isFunctionType(props[key]))
      .map(key => ({
        key: propsMapping[key] || key,
        fn: customEvent => props[key](customEvent.detail, customEvent)
      }));
    fns.forEach(({ key, fn }) => current.addEventListener(key, fn));
  }
  return () => {
    if (current) {
      fns.forEach(({ key, fn }) => current.removeEventListener(key, fn));
    }
  };
}, [propsMapping, props, ref]);

Custom Mapping

If the consumer uses different prop names (e.g., text instead of label ), pass a mapping object to the hook:

const [elementProps, ref] = useWebComponentsCustomElement(props, {
  text: 'label',
  selected: 'option',
  mapList: 'options',
  onChange: 'onOptionChange'
});

Final Hook Implementation

import React from 'react';

function getType(obj) { return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1'); }
function isFunctionType(obj) { return getType(obj) === 'Function'; }
function isObjectOrArrayType(obj) { return ['Object', 'Array'].includes(getType(obj)); }

const useWebComponentsCustomElement = (props, propsMapping = {}) => {
  const ref = React.createRef();

  React.useEffect(() => {
    const { current } = ref;
    let fns;
    if (current) {
      fns = Object.keys(props)
        .filter(key => isFunctionType(props[key]))
        .map(key => ({
          key: propsMapping[key] || key,
          fn: customEvent => props[key](customEvent.detail, customEvent)
        }));
      fns.forEach(({ key, fn }) => current.addEventListener(key, fn));
    }
    return () => {
      if (current) {
        fns.forEach(({ key, fn }) => current.removeEventListener(key, fn));
      }
    };
  }, [propsMapping, props, ref]);

  const elementProps = Object.keys(props)
    .filter(key => !isFunctionType(props[key]))
    .reduce((acc, key) => {
      const prop = props[key];
      const computedKey = propsMapping[key] || key;
      if (isObjectOrArrayType(prop)) {
        return { ...acc, [computedKey]: JSON.stringify(prop) };
      }
      return { ...acc, [computedKey]: prop };
    }, {});

  return [elementProps, ref];
};

Conclusion

The custom Hook automatically wraps a Web Component for use in React, handling JSON conversion of arrays/objects, preserving primitive values, removing function props from attributes, registering them as event listeners, and cleaning up listeners on unmount. This pattern keeps React code clean while leveraging reusable Web Components.

Web Components are not meant to replace frameworks like React or Vue; they complement them by providing native, framework‑agnostic reusable UI elements.

frontendJavaScriptreactWeb ComponentsCustom Hook
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.