# @webflow/react

The core React integration package for building Webflow code components. This package provides the essential tools for declaring components, rendering them on both client and server, and accessing Webflow-specific context.

## Installation

```bash
npm i @webflow/react
```

### Peer Dependencies

This package requires the following peer dependencies:

```bash
npm i react react-dom
```

## Usage

### Declaring Components

Use `declareComponent` to create a Webflow code component definition. This should be the default export from your `*.webflow.tsx` file.

#### Basic Example

```tsx
import { declareComponent } from "@webflow/react";
import { props } from "@webflow/data-types";

function Button({ text, link }) {
  return (
    <a href={link?.href} target={link?.target}>
      {text}
    </a>
  );
}

export default declareComponent(Button, {
  name: "Button",
  description: "A customizable button component",
  props: {
    text: props.Text({ name: "Text", defaultValue: "Click me" }),
    link: props.Link({ name: "Link" }),
  },
});
```

#### With Decorators

Decorators allow you to wrap your component with additional functionality, such as CSS-in-JS providers:

```tsx
import { declareComponent } from "@webflow/react";
import { emotionShadowDomDecorator } from "@webflow/emotion-utils";

export default declareComponent(MyComponent, {
  name: "My Component",
  decorators: [emotionShadowDomDecorator],
});
```

#### With Options

```tsx
export default declareComponent(MyComponent, {
  name: "My Component",
  props: {
    // ... your props
  },
  options: {
    applyTagSelectors: true, // Provide styles targeting tag selectors (default: false)
    ssr: "prerender", // Enable server-side rendering awaiting Suspense boundaries (default: true)
  },
});
```

### Using Webflow Context

Access Webflow-specific context data in your components using the `useWebflowContext` hook:

```tsx
import { useWebflowContext } from "@webflow/react";

function MyComponent() {
  const { mode, interactive, locale } = useWebflowContext();

  return (
    <div>
      <p>Mode: {mode}</p>
      <p>Interactive: {interactive ? "Yes" : "No"}</p>
      <p>Locale: {locale}</p>
    </div>
  );
}
```

**Context Properties:**

- `mode` - The current mode (`"design"` or `"preview"`)
- `interactive` - Whether the component is in an interactive state
- `locale` - The current locale (e.g., `"en-US"`)

## Prerender data and hydration

Three hooks make async data resolved during **prerender** available **synchronously** when the Webflow runtime hydrates the component (via `options.data`). Values must be **JSON-serializable**. Each read hook returns an **object** — destructure it: `const { data } = useSuspenseData(...)` / `usePrerenderData(...)`.

- **`useSuspenseData(key, loader)`** — the hook owns the fetch. Use it for components that fetch their own data (no external data library). The loader runs and suspends during prerender, the result is recorded under `key`, and the recorded value is returned synchronously on the client. Returns `{ data }`.
- **`usePrerenderData<T>(key)`** — read-only and **never fetches or suspends**. Use it for components whose fetching is owned by a Suspense-capable library (React Query `useSuspenseQuery`, SWR `{ suspense: true }`). It returns `{ data }`, the prerendered value (or `undefined`). Feed `data` back to the library as `initialData` / `fallbackData`, then call **`useHydrateData(key, value)`** — with the **same `key`** — to record the library's resolved value. Define the key once and reuse it for `usePrerenderData`, your library's query key, and `useHydrateData`.

Shared rules:

- **Not** a general client cache: there is no built-in invalidation or refetch. Use normal React patterns (or a data library) for updates after the first paint.
- Use a **stable key** per logical resource — either a plain string (used as-is, e.g. a URL + query string like `useSuspenseData("/cities?offset=20", loader)`) or an array of `string`, `number`, or `boolean` segments (e.g. `useSuspenseData(["todo", todoId], loader)`). Array segments serialize to colon-joined strings (`["t", "x"]` → `"t:x"` in `ServerPrerenderResult.data`). This is a cache identity, not a React deps array. **First-write-wins** if keys collide (applies to both `useSuspenseData`'s auto-record and `useHydrateData`).
- The Webflow renderer wraps every code component in a host **`SuspenseBoundary`** (alongside **`ErrorBoundary`**), so you do **not** need your own `<Suspense>` for these hooks to work. Add a customer `<Suspense>` only when you want a custom loading UI.
- If a suspend is caught only by the host boundary (no customer fallback), SSR may emit an HTML comment (`<!-- webflow-cc:suspense-fallback ... -->`) for debugging — use **`ssr: 'prerender'`** with `useSuspenseData` / `usePrerenderData` so data resolves before paint.
- With **`options.ssr: false`** / no prerender, `useSuspenseData` still runs the `loader` on the client (Suspense). **Seeded** values (`data[key]` from prerender) only apply when the component tree is under **`PrerenderDataProvider`** with that `data` object (see **`ClientRenderer.hydrate`** and **`ClientRenderer.render`** below).
- **`ClientRenderer.hydrate`** and **`ClientRenderer.render`** wrap the subtree in **`PrerenderDataProvider`** with **`mode="seeded"`** and **`data={options?.data ?? {}}`**. If `data` contains `key`, `useSuspenseData` / `usePrerenderData` return it synchronously. Otherwise `useSuspenseData`'s `loader` runs (Suspense) with a **per-provider** keyed cache (same `key` under one provider reuses one `loader()`; first `loader` wins if the same `key` is used with different loaders). Outside `PrerenderDataProvider`, there is no keyed cache.
- **`ClientRenderer.mount`** only creates a **`ReactDOM.Root`**; it does not wrap with **`PrerenderDataProvider`** or render content.
- Pass **`options.data`** on each **`render`** when prerender seeds must stay available (each call replaces the root tree; `{}` means no seeds for that update).
- **Prerender** uses **`PrerenderDataProvider`** with **`mode="collect"`** and a mutable **`data`** object; `ServerRenderer.prerenderToString` writes loader / hydrate results into `data` and returns them as `ServerPrerenderResult.data`.

`ServerRenderer.prerenderToString` always resolves to a **`ServerPrerenderResult`**: `{ html, styles?, data? }` (see `@webflow/data-types`). The Webflow host is responsible for persisting `data` onto the page and passing it back into **`ClientRenderer.hydrate`** and **`ClientRenderer.render`** (e.g. `{ ..., data }` on each update that should keep seeds).

### Fetching your own data (`useSuspenseData`)

For components that fetch their own data (no external data library), use `useSuspenseData`. It owns the whole flow: during prerender it runs the loader, suspends until it resolves, and records the result under `key`; on the client that recorded value is returned synchronously, so the component paints with real content on first render — no refetch, no loading flash. You don't call `useHydrateData` here; `useSuspenseData` records automatically.

```tsx
import { props } from "@webflow/data-types";
import { declareComponent, useSuspenseData } from "@webflow/react";

type Profile = { name: string; bio: string };

async function fetchProfile(id: string): Promise<Profile> {
  const res = await fetch(`/api/profiles/${id}`);
  return res.json();
}

function ProfileCard({ id }: { id: string }) {
  // Array key (joined with ":") — or a string like `/api/profiles/${id}`.
  const { data } = useSuspenseData<Profile>(["profile", id], () =>
    fetchProfile(id)
  );
  return (
    <article>
      <h1>{data.name}</h1>
      <p>{data.bio}</p>
    </article>
  );
}

export default declareComponent(ProfileCard, {
  name: "Profile Card",
  props: { id: props.Text({ name: "Profile ID", defaultValue: "42" }) },
  // Resolve the loader during prerender so the data is ready on first paint.
  options: { ssr: "prerender" },
});
```

Notes:

- The resolved value must be **JSON-serializable** and the `loader` should be **pure for a given key** — it may run more than once (e.g. under React Strict Mode), and first-write-wins on key collision.
- The host already wraps the component in a `SuspenseBoundary`, so no `<Suspense>` is needed; add one only for a custom loading UI.
- Unlike the data-library form, loader errors are **serialized into `ServerPrerenderResult.data` during prerender** (prerender still resolves; the host `ErrorBoundary` renders empty for that subtree) and **rethrown on the client** from the seed so they surface consistently through the host `ErrorBoundary`.

### Using a data library (React Query / SWR)

`usePrerenderData` (read-only) lets a Suspense-mode data library participate in prerender hydration without double-fetching. The library keeps ownership of fetching, caching, and refetch. Pair it with `useHydrateData` to record the library's resolved value.

```tsx
// React Query — define the key once and reuse it everywhere
const key = ["cities", offset];
const { data: initialData } = usePrerenderData<CitiesPage>(key);
const { data } = useSuspenseQuery({
  queryKey: key,
  queryFn: () => fetchCitiesPage(offset),
  initialData,
});
useHydrateData(key, data);
```

```tsx
// SWR
const key = ["cities", offset];
const { data: fallbackData } = usePrerenderData<CitiesPage>(key);
const { data } = useSWR(key, () => fetchCitiesPage(offset), {
  suspense: true,
  fallbackData,
});
useHydrateData(key, data); // SWR types `data` as `T | undefined`; useHydrateData accepts that (records nothing if undefined)
```

The `key` is a plain string or array, so the same value doubles as your library's query key — no separate handle to thread, and no chance of the prerender key drifting from the library key.

Requirements and caveats when using a data library:

- The library **must be in Suspense mode during prerender** (`useSuspenseQuery`, SWR `{ suspense: true }`). `ssr: 'prerender'` only awaits Suspense; a non-suspense library resolves via state/effects that prerender never awaits, and `useHydrateData` would record `undefined`.
- `useHydrateData` is a **hook**: call it at the top level **during render**, not in `useEffect` — effects don't run during prerender, so the value wouldn't be captured. (The rules of hooks enforce this placement.)
- `usePrerenderData` **does not transport errors**: a library rejection during prerender surfaces through the host `ErrorBoundary`, while on the client the library re-fetches and owns its own error UI. (`useSuspenseData` serializes loader errors and rethrows them on the client.)
- With `useSuspenseQuery` + `initialData` (or SWR + `fallbackData`), the first client render is synchronous (no suspense, no hydration mismatch); the library then revalidates in the background per its own config (`staleTime`, etc.).

## Server-Side Rendering

This package provides a server-side renderer for React components. The `ServerRenderer` provides:

- Server-side rendering with `renderToString` and `renderToStream`
- **`prerenderToString`** — awaits Suspense boundaries (`onAllReady`) and returns `{ html, data? }` (plus `styles?` when using Emotion/styled-components server packages)
- Support for creating slot elements with `createSlot`
- Automatic handling of Webflow context during SSR

**Note:** For CSS-in-JS libraries like Emotion or styled-components, use their respective server renderers instead:

- `@webflow/emotion-utils/server`
- `@webflow/styled-components-utils/server`

Configure the server renderer in your `webflow.json` file:

```json
{
  "library": {
    "renderer": {
      "server": "@webflow/emotion-utils/server"
    }
  }
}
```

## API Reference

### `declareComponent`

Creates a Webflow code component definition.

**Type:**

```typescript
<P extends {}>(
  Component: React.ComponentType<P>,
  data: ComponentData<P, React.ReactNode, React.ComponentType<P>>
) => ComponentDefinition<React.ComponentType<P>, React.ReactNode, P>;
```

**Parameters:**

- `Component` - The React component to render
- `data` - Component metadata and configuration
  - `data.name` - The display name of the component
  - `data.description` (optional) - Description of the component
  - `data.group` (optional) - Group for organizing components
  - `data.props` (optional) - Component props configuration
  - `data.options` (optional) - Additional options
    - `data.options.applyTagSelectors` (optional) - Provide tag selector styles (default: `false`)
    - `data.options.ssr` (optional) - Enable server-side rendering (default: `true`)
  - `data.decorators` (optional) - Array of decorator functions

**Returns:** A Webflow code component definition

### `useSuspenseData`

`useSuspenseData(key, loader)` — see [Fetching your own data](#fetching-your-own-data-usesuspensedata).

**Type:**

```typescript
// `Key` shorthand below: a plain string is used as-is (handy for URLs); an array is joined
// with ":". Define it once and reuse it. Inferred from your argument — there's no exported
// type to import.
type Key = string | readonly (string | number | boolean)[];

function useSuspenseData<T>(key: Key, loader: () => Promise<T>): { data: T };
```

### `usePrerenderData`

`usePrerenderData<T>(key)` — read-only seed for data libraries. See [Using a data library](#using-a-data-library-react-query--swr).

**Type:**

```typescript
function usePrerenderData<T>(key: Key): { data: T | undefined };
```

### `useHydrateData`

`useHydrateData(key, value)` — records a value fetched by your own Suspense-capable data library into the prerender snapshot. Pair it with `usePrerenderData(key)`, reusing the same `key`. See [Using a data library](#using-a-data-library-react-query--swr).

**Type:**

```typescript
function useHydrateData<T>(key: Key, value: T | undefined): void;
```

Pass the **same `key`** you gave `usePrerenderData` and your data library — define it once and reuse it so it can't drift. `value` accepts `undefined` (a no-op), so libraries whose `data` stays `T | undefined` (e.g. SWR) need no cast. Call it during render — it's a hook, so the rules of hooks keep it out of effects and conditionals.

### `useWebflowContext`

Hook to access Webflow context data.

**Type:**

```typescript
() => WebflowContextType;
```

**Returns:**

```typescript
{
  mode: "design" | "preview";
  interactive: boolean;
  locale: string;
}
```

### `ClientRenderer`

A factory that creates a client-side renderer for a React component.

**Type:**

```typescript
ComponentClientRendererFactory<
  React.ComponentType<ComponentRuntimeProps<React.ReactNode>>,
  ReactDOM.Root,
  React.ReactNode
>;
```

**Methods:**

- `mount(domNode)` - Creates a `ReactDOM.Root` on the DOM node only (no `PrerenderDataProvider`, no initial render).
- `hydrate(domNode, props?, options?)` - Hydrates a server-rendered tree wrapped in **`PrerenderDataProvider`** with **`mode="seeded"`** and **`data` from `options?.data ?? {}`** (replays prerender `data` for `useSuspenseData` / `usePrerenderData`).
- `render(root, props?, options?)` - Renders to an existing root wrapped in **`PrerenderDataProvider`** with **`mode="seeded"`** and **`data` from `options?.data ?? {}`** (same as `hydrate` for prerender-data context). Pass `options.data` on each call when seeds must remain available.
- `createSlot(name)` - Creates a named slot element for component composition

### `ServerRenderer`

A factory that creates a server-side renderer for a React component.

**Type:**

```typescript
ComponentServerRendererFactory<
  React.ComponentType<ComponentRuntimeProps<React.ReactNode>>,
  PipeableStream,
  ReactDOMServer.RenderToPipeableStreamOptions,
  React.ReactNode,
  ReactDOMServer.ServerOptions
>;
```

**Methods:**

- `renderToStream(props?, options?, streamOptions?)` - Renders component to a pipeable stream
- `renderToString(props?, options?, stringOptions?)` - Renders component to a string
- `prerenderToString(props?, options?, prerenderOptions?)` - Prerender after Suspense resolves; returns `Promise<ServerPrerenderResult>`
- `createElement(props?, options?)` - Creates a React element with the component
- `createSlot(name)` - Creates a named slot element for component composition

### `applyDecorators`

Utility function to apply an array of decorators to a component.

**Type:**

```typescript
<P extends {}>(
  Component: React.ComponentType<P>,
  decorators: Array<
    (Component: React.ComponentType<P>) => React.ComponentType<P>
  >
) => React.ComponentType<P>;
```

## License

MIT
