A React component wrapper for web components that has graduated from labs and now serves as a proxy to @lit/react
npx @tessl/cli install tessl/npm-lit-labs--react@2.1.0React integration for Web Components and Reactive Controllers. This package has graduated from labs and now serves as a proxy that re-exports functionality from @lit/react while maintaining compatibility during the ecosystem migration.
npm install @lit-labs/reactimport { createComponent } from "@lit-labs/react";
import { useController } from "@lit-labs/react/use-controller.js";For CommonJS:
const { createComponent } = require("@lit-labs/react");
const { useController } = require("@lit-labs/react/use-controller.js");import * as React from 'react';
import { createComponent } from '@lit-labs/react';
import { MyElement } from './my-element.js';
// Create React component wrapper for custom element
export const MyElementComponent = createComponent({
tagName: 'my-element',
elementClass: MyElement,
react: React,
events: {
onactivate: 'activate',
onchange: 'change',
},
});
// Use the component in JSX
function App() {
const [isActive, setIsActive] = React.useState(false);
return (
<MyElementComponent
active={isActive}
onactivate={(e) => setIsActive(e.active)}
onchange={(e) => console.log('Changed:', e.detail)}
/>
);
}@lit-labs/react provides two main utilities:
@lit/react to maintain compatibility during migrationCreates React component wrappers for custom elements with proper property and event handling.
/**
* Creates a React component for a custom element
* @param options Configuration object for the wrapper component
* @returns React component that wraps the custom element
*/
function createComponent<I extends HTMLElement, E extends EventNames = {}>(
options: Options<I, E>
): ReactWebComponent<I, E>;
interface Options<I extends HTMLElement, E extends EventNames = {}> {
/** The React module, typically imported from the 'react' npm package */
react: typeof React;
/** The custom element tag name registered via customElements.define */
tagName: string;
/** The custom element class registered via customElements.define */
elementClass: Constructor<I>;
/** Object mapping React prop names to custom element event names */
events?: E;
/** React component display name, used in debugging messages */
displayName?: string;
}
type Constructor<T> = { new (): T };
type EventNames = Record<string, EventName | string>;Usage Examples:
import * as React from 'react';
import { createComponent } from '@lit-labs/react';
import { MyElement } from './my-element.js';
// Basic component creation
const MyElementComponent = createComponent({
tagName: 'my-element',
elementClass: MyElement,
react: React,
});
// Component with event mapping
const InteractiveComponent = createComponent({
tagName: 'interactive-element',
elementClass: InteractiveElement,
react: React,
events: {
onactivate: 'activate',
onchange: 'change',
onclick: 'click',
},
displayName: 'InteractiveElement',
});React hook for using Reactive Controllers within React components.
/**
* Creates and stores a stateful ReactiveController instance with React lifecycle
* @param React The React module that provides the base hooks (useState, useLayoutEffect)
* @param createController Function that creates a controller instance given a host
* @returns The controller instance
*/
function useController<C extends ReactiveController>(
React: typeof window.React,
createController: (host: ReactiveControllerHost) => C
): C;
type ControllerConstructor<C extends ReactiveController> = {
new (...args: Array<any>): C;
};Usage Examples:
import * as React from 'react';
import { useController } from '@lit-labs/react/use-controller.js';
import { MouseController } from '@example/mouse-controller';
// Create a React hook using a controller
const useMouse = () => {
const controller = useController(React, (host) => new MouseController(host));
return controller.position;
};
// Use the controller hook in a React component
const Component = () => {
const mousePosition = useMouse();
return (
<pre>
x: {mousePosition.x}
y: {mousePosition.y}
</pre>
);
};
// Direct controller usage
const MyComponent = () => {
const myController = useController(React, (host) => new MyCustomController(host));
return (
<div>
<p>Controller state: {myController.value}</p>
<button onClick={() => myController.increment()}>
Increment
</button>
</div>
);
};/**
* Type used to cast event names with specific event types for better typing
*/
type EventName<T extends Event = Event> = string & {
__eventType: T;
};
/**
* Type of the React component wrapping the web component
*/
type ReactWebComponent<
I extends HTMLElement,
E extends EventNames = {}
> = React.ForwardRefExoticComponent<
React.HTMLAttributes<I> & Partial<Omit<I, keyof HTMLElement>> & React.RefAttributes<I>
>;
/**
* Creates a type for props of a web component used directly in React JSX
*/
type WebComponentProps<I extends HTMLElement> = React.DetailedHTMLProps<
React.HTMLAttributes<I>,
I
>;
/**
* Type for constructor functions that create ReactiveController instances
*/
type ControllerConstructor<C extends ReactiveController> = {
new (...args: Array<any>): C;
};
/**
* External types from @lit/reactive-element (not part of @lit-labs/react API)
* These interfaces are imported when using useController
*/
interface ReactiveController {
hostConnected?(): void;
hostDisconnected?(): void;
hostUpdate?(): void;
hostUpdated?(): void;
}
interface ReactiveControllerHost {
addController(controller: ReactiveController): void;
removeController(controller: ReactiveController): void;
requestUpdate(): void;
readonly updateComplete: Promise<boolean>;
}import type { EventName } from '@lit-labs/react';
// Define typed events for better type safety
const MyElementComponent = createComponent({
tagName: 'my-element',
elementClass: MyElement,
react: React,
events: {
onClick: 'pointerdown' as EventName<PointerEvent>,
onChange: 'input' as EventName<InputEvent>,
},
});
// Now event callbacks are properly typed
<MyElementComponent
onClick={(e: PointerEvent) => console.log('Pointer event:', e)}
onChange={(e: InputEvent) => console.log('Input event:', e)}
/>declare module 'react' {
namespace JSX {
interface IntrinsicElements {
'my-element': WebComponentProps<MyElement>;
}
}
}
// Now you can use the element directly in JSX
<my-element customProp={value} onCustomEvent={handler} />createComponent will warn if reserved React properties are found on the element prototypeuseController handles React Strict Mode correctly and manages controller lifecycle automaticallyComponents created with createComponent support server-side rendering when used with @lit/ssr-react. The wrapper handles:
defer-hydration attribute to prevent hydration warnings_$litProps$ for server rendering// SSR usage (works automatically with @lit/ssr-react)
const ServerRenderedComponent = createComponent({
tagName: 'my-element',
elementClass: MyElement,
react: React,
});
// Component will render properly on server and hydrate correctly on client
<ServerRenderedComponent customProp={value} />This package is deprecated and serves as a compatibility layer. For new projects, use @lit/react directly:
// Old (deprecated but still works)
import { createComponent } from '@lit-labs/react';
// New (recommended)
import { createComponent } from '@lit/react';