How to include Preact component with hooks in React app?

Viewed 2401

This sounds possibly unusual but I'm trying to import a Preact component with Preact hooks in a React app. Unfortunately, doing so throws an Cannot read property '__H' of undefined. More to this below.

Suppose, to keep this question simple, the Preact component lives in a package like this:

// src/components/index.js
import { h } from 'preact';
import { useRef } from 'preact/hooks';

const PreactComponent = () => {
  const ref = useRef(null);
  return <div ref={ref}>Hello World</div>;
};

// package.json
{
  "name: "mypackage",
  "main": "dist/index.js", // Output by Webpack, with src/components/index.js as entrypoint.
  ...
}

Standard stuff. It's imported in the React app:

...
import { PreactComponent } from 'mypackage';

const MyComponent = () => {
  return <PreactComponent />
};

This throws Unhandled Rejection (TypeError): Cannot read property '__H' of undefined.

It's definitely Preact hooks related as removing useRef in the Preact component results in the component rendering fine. And as you'll see above, the hook is defined inside a function, as it should be.

Has anyone tried to use a Preact component with hooks in React? How did you go about it?

1 Answers

This throws because the React renderer isn't aware of Preact-specific internals. If we check the transpiled output of JSX we get this:

// Won't work, PreactComponent is not a React component
React.createElement(PreactComponent, null)

The easiest way to resolve this is to supply a DOM node where Preact can render into.

import React, { useRef, useEffect } from "react";
import { render, h } from "preact";
import { PreactComponent } from "./whatever";

function ReactPreactBridge() {
  // Get the raw DOM node to render into
  const ref = useRef(null)

  useEffect(() => {
    if (ref.current) {
      // Can't use two different JSX constructors in
      // the same file, so we're writing the JSX output
      // manually. (h is the same as createElement)
      render(h(PreactComponent, null), ref.current)
    }

    return () => {
      // Clear Preact rendered tree when the parent React
      // component unmounts
      render(null, ref.current);
    }
  }, [ref.current]);

  return <div ref={ref} />
}
Related