@evanion/react-widget
@evanion/react-widget 0.3.0 and its patch releases up to 0.3.3, the version on npm. Values on this page were produced by running the source at f9b1d93, a later commit than the 0.3.0 tag, and are not re-executed.
Bridge the gap between CMS JSON and type-safe React components.
Content management systems (CMS) allow editors to build pages by selecting widgets and filling in their data. But because this data is often just plain JSON, there is no native way to ensure that a CMS payload is compatible with your React components before it is rendered.
@evanion/react-widget provides a structured orchestration layer that maps CMS widget types to React components, while using TypeScript’s power to ensure that your items and their props are valid at compile time.
The Problem: The “Untyped JSON” Gap
When you render CMS data in React, you usually end up with a large switch statement or a lookup object that maps a string type to a component. This approach has several weaknesses:
- Type Blindness: TypeScript doesn’t know which props are required for a
listingwidget versus aherowidget. - Runtime Crashes: A missing required prop in the JSON payload can cause a component to crash, taking down the entire page.
- Maintenance Burden: Adding a new widget requires updating the map, the types, and the validation logic in multiple places.
The Solution: A Typed Component Map
The core of the library is createWidgets. This function takes a component map—an object where the keys are the widget types and the values are the React components that render them. It then returns a Widgets component.
The Widgets component takes an array of items and renders them in order. For example, in the Baize shop, we map a listing type to a ListingCard component and a booking type to a BookingStrip:
import { } from '@evanion/react-widget';
const = ({ , }: { : string; : number }) => (
< ="listing">
<>{}</>
<>{} kr</>
</>
);
const = ({
,
,
}: {
: number;
: boolean;
}) => (
< ="booking">
<>{} tables</>
<>{ ? 'tonight' : 'this week'}</>
</>
);
// Call once, at module scope: each call returns a new Widgets.
const { } = ({
: { : , : },
});
export function () {
return (
<
={[
{ : 'b1', : 'booking', : { : 4, : true } },
{
: 'g1',
: 'listing',
: { : 'Brass: Birmingham', : 649 },
},
]}
/>
);
}By defining your widgets this way, TypeScript can verify your data immediately. If you change a price from a number to a string in your item array, you’ll get a compile error before the code ever runs.
The react-widget Vocabulary
To help you build your UI, we use these terms:
- Component Map: The object passed to
createWidgetsthat links types to components. - Item: A single entry in the data array (plain JSON).
- Widget: The actual React component rendered for an item’s type.
- Region: The area of the page rendered by a single
<Widgets>component. - Chrome: The layout elements surrounding the widgets.
chrome.wrapperwraps the whole region, andchrome.itemwraps each individual widget. ctx: Page-level data (like a current currency) passed to every widget in the region via the<Widgets ctx={…}>prop.
Item Anatomy
Every item consists of these five fields:
id: A stable string used for the React key and thedata-widget-idattribute.type: A key from the component map.props: The data passed to the component (excludingchildrenandctx).meta: Optional placement data (e.g., grid spans) read by thechrome.itemwrapper.children: Optional nested items, which are rendered as the component’schildren.
Core Capabilities
@evanion/react-widget handles the heavy lifting of turning JSON data into a type-safe React tree:
| Goal | Tool |
|---|---|
| Render a region from an item array | createWidgets, which returns the Widgets component |
| Type-safe item arrays in variables | defineItems, which ensures items match the component map |
| CMS payload validation | validateItems, which checks the JSON before rendering |
| Server Component support | Widgets is RSC-compatible and supports async widgets |
| Custom layouts for regions/items | chrome.wrapper and chrome.item |
| Global data shared by all widgets | The ctx prop on <Widgets> |
| Independent loading for slow widgets | chrome.suspense, giving each widget its own boundary |
Design Boundaries
To keep the library lean, a few responsibilities are left to your application:
- Context: The library does not create a React context. Use the
ctxprop to share page-level data. - Error Boundaries: If a widget crashes, it can take down the page. Wrap your
chrome.itemin an error boundary to isolate failures. - Virtualization:
Widgetsrenders all items in the array. For extremely long lists, integrate a virtualization library. - Render-time Validation:
Widgetssimply skips items it cannot render. To catch these errors before they ship, runvalidateItemsin your build or CI pipeline.
Render Your First Listing
You now have the mental model: a component map, an item array, and the Widgets component that brings them together. Getting started will guide you through installing the package and rendering a Baize listing to HTML.