@wrdagency/react-islands
Version:
Created by Kyle Cooper at [WRD.agency](https://webresultsdirect.com)
103 lines (99 loc) • 3.15 kB
JavaScript
import { jsx } from 'react/jsx-runtime';
import { createRoot } from 'react-dom/client';
import { useRef, useEffect } from 'react';
/**
* Internal component for rendering raw HTML in a React component.
*/
function RawHTML({ html }) {
const ref = useRef(null);
// important to not have ANY deps
useEffect(() => {
if (ref.current) {
ref.current.outerHTML = html;
}
}, []);
return jsx("script", { ref: ref });
}
/**
* Create a higher-order component with certain fixed.
*
* Useful for quickly creating multiple variants of the same component to use as islands.
*
* @param component FC<T>
* @param setProps Partial<T>
* @returns FC<T>
*/
function withProps(component, setProps) {
return (props) => {
return component({ ...props, ...setProps });
};
}
/**
* Checks if the current script is running in a pre-render.
*
* @returns boolean
*/
function isServer() {
return !(typeof window != "undefined" && window.document);
}
class Island {
component;
renderOptions;
constructor(component, opts = {}) {
this.component = component;
this.renderOptions = {
shouldHydrate: opts.shouldHydrate ?? true,
multiple: opts.multiple ?? false,
keepChildren: opts.keepChildren ?? false,
};
}
getProps(element) {
const { keepChildren } = this.renderOptions;
let props = {};
try {
const json = element.dataset.props || "{}";
if (json) {
const parsed = JSON.parse(json);
if (typeof parsed !== "object" || Array.isArray(parsed)) {
throw new Error(`Parsed JSON is not a valid dictionary object: '${json}'`);
}
if (parsed) {
// Ignore null.
props = parsed;
}
}
}
catch (e) {
console.warn("Could not parse JSON props for React Island.");
console.error(e);
}
if (keepChildren) {
props.children = jsx(RawHTML, { html: element.innerHTML });
}
return props;
}
getRoots(name) {
const selector = `[data-island="${name}"]`;
const { multiple } = this.renderOptions;
const nodes = [...document.querySelectorAll(selector)];
if (!nodes) {
console.warn(`Could not render React Island because DOM node (${selector}) could not be found.`);
return [];
}
if (nodes.length > 1 && !multiple) {
console.warn(`Multiple elements matched React Island selector (${selector}) but multiple was not enabled. Choosing first element as root.`);
return [nodes[0]];
}
return nodes;
}
render(name) {
this.getRoots(name).forEach((element) => {
const props = this.getProps(element);
const Component = withProps(this.component, props);
const root = createRoot(element);
root.render(jsx(Component, {}));
return root;
});
}
}
export { Island, isServer, withProps };