A React component wrapper for web components and React hooks for Reactive Controllers.
npx @tessl/cli install tessl/npm-lit--react@1.0.0@lit/react provides React integration for Web Components and Reactive Controllers, enabling seamless interoperability between React applications and web components. The library offers utilities for property binding, event handling, and controller composition while maintaining type safety and following React patterns.
npm install @lit/reactimport { createComponent } from "@lit/react";
import type { EventName, ReactWebComponent, WebComponentProps } from "@lit/react";For the controller hook:
import { useController } from "@lit/react/use-controller.js";
import type { ControllerConstructor } from "@lit/react/use-controller.js";import * as React from 'react';
import { createComponent } from '@lit/react';
import { MyElement } from './my-element.js';
export const MyElementComponent = createComponent({
tagName: 'my-element',
elementClass: MyElement,
react: React,
events: {
onactivate: 'activate',
onchange: 'change',
},
});
// Usage in JSX
<MyElementComponent
active={isActive}
onactivate={(e) => setIsActive(e.active)}
/>import * as React from 'react';
import { useController } from '@lit/react/use-controller.js';
import { MouseController } from '@example/mouse-controller';
const useMouse = () => {
const controller = useController(React, (host) => new MouseController(host));
return controller.position;
};
const Component = () => {
const mousePosition = useMouse();
return <pre>x: {mousePosition.x} y: {mousePosition.y}</pre>;
};@lit/react is built around two main integration patterns:
createComponent() creates React components that properly bridge React props and web component properties/eventsuseController() adapts Lit's Reactive Controller lifecycle to React's hook systemuseLayoutEffect@lit/ssr-reactCreates React component wrappers for custom elements with proper property binding and event handling, addressing React's default limitations with web components.
/**
* Creates a React component for a custom element with proper property binding and event handling
* @param options Configuration object for the component wrapper
* @returns React ForwardRefExoticComponent 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 event prop names to custom element event names */
events?: E;
/** React component display name for debugging (defaults to element class name) */
displayName?: string;
}
type Constructor<T> = { new (): T };
type EventNames = Record<string, EventName | string>;React hook that creates and manages Reactive Controllers using React's lifecycle, enabling controller composition and state management patterns from Lit in React components.
/**
* Creates and stores a stateful ReactiveController instance with React lifecycle integration
* @param React The React module providing useState and useLayoutEffect hooks
* @param createController Function that creates a controller instance given a host
* @returns The created controller instance
*/
function useController<C extends ReactiveController>(
React: typeof window.React,
createController: (host: ReactiveControllerHost) => C
): C;
// ReactiveController and ReactiveControllerHost are imported from @lit/reactive-element
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>;
}
type ControllerConstructor<C extends ReactiveController> = {
new (...args: Array<any>): C;
};Type utilities for enhanced TypeScript integration and type-safe event handling.
/**
* Props type for web component used directly in React JSX
*/
type WebComponentProps<I extends HTMLElement> = React.DetailedHTMLProps<
React.HTMLAttributes<I>,
I
> & ElementProps<I>;
/**
* Type of the React component wrapping the web component (return type of createComponent)
*/
type ReactWebComponent<I extends HTMLElement, E extends EventNames = {}> =
React.ForwardRefExoticComponent<
ComponentProps<I, E> & React.RefAttributes<I>
>;
// Helper types used in the API
type ElementProps<I> = Partial<Omit<I, keyof HTMLElement>>;
type ComponentProps<I, E extends EventNames = {}> = Omit<
React.HTMLAttributes<I>,
keyof E | keyof ElementProps<I>
> & EventListeners<E> & ElementProps<I>;
type EventListeners<R extends EventNames> = {
[K in keyof R]?: R[K] extends EventName
? (e: R[K]['__eventType']) => void
: (e: Event) => void;
};
/**
* Type for casting event names with event types for better typing of event handler props
* @example
* events: {
* onfoo: 'foo' as EventName<FooEvent>,
* }
*/
type EventName<T extends Event = Event> = string & {
__eventType: T;
};The library includes a React-driven implementation of ReactiveControllerHost that bridges Lit's controller lifecycle with React hooks through an internal ReactControllerHost class that manages the controller lifecycle using React's useState and useLayoutEffect hooks.
Enhanced event handling with type safety for custom element events.
Basic Event Mapping:
const Component = createComponent({
tagName: 'my-element',
elementClass: MyElement,
react: React,
events: {
onchange: 'change',
oncustom: 'custom-event',
},
});Type-Safe Event Handling:
import type { EventName } from '@lit/react';
const Component = createComponent({
tagName: 'my-element',
elementClass: MyElement,
react: React,
events: {
onClick: 'pointerdown' as EventName<PointerEvent>,
onChange: 'input',
},
});
// Usage with properly typed event handlers
<Component
onClick={(e: PointerEvent) => console.log('Pointer event:', e)}
onChange={(e: Event) => console.log('Input event:', e)}
/>The library includes built-in support for server-side rendering when used with @lit/ssr-react:
// SSR support is automatically enabled when litSsrReactEnabled is true
// or when React.createElement.name === 'litPatchedCreateElement'The library provides development-time warnings for potential integration issues: