Skip to Content
React WidgetOverview

These pages document @evanion/react-widget 0.3.3, the version on npm.

@evanion/react-widget

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 listing widget versus a hero widget.
  • 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 createWidgets that 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.wrapper wraps the whole region, and chrome.item wraps 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 the data-widget-id attribute.
  • type: A key from the component map.
  • props: The data passed to the component (excluding children and ctx).
  • meta: Optional placement data (e.g., grid spans) read by the chrome.item wrapper.
  • children: Optional nested items, which are rendered as the component’s children.

Core Capabilities

@evanion/react-widget handles the heavy lifting of turning JSON data into a type-safe React tree:

GoalTool
Render a region from an item arraycreateWidgets, which returns the Widgets component
Type-safe item arrays in variablesdefineItems, which ensures items match the component map
CMS payload validationvalidateItems, which checks the JSON before rendering
Server Component supportWidgets is RSC-compatible and supports async widgets
Custom layouts for regions/itemschrome.wrapper and chrome.item
Global data shared by all widgetsThe ctx prop on <Widgets>
Independent loading for slow widgetschrome.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 ctx prop to share page-level data.
  • Error Boundaries: If a widget crashes, it can take down the page. Wrap your chrome.item in an error boundary to isolate failures.
  • Virtualization: Widgets renders all items in the array. For extremely long lists, integrate a virtualization library.
  • Render-time Validation: Widgets simply skips items it cannot render. To catch these errors before they ship, run validateItems in 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.

Last updated on