# Uniform SDK for React

> Comprehensive guide to integrating Uniform with React, including Canvas composition rendering, Context personalization, A/B testing, and adding Uniform to existing projects.

Source: https://docs.uniform.app/docs/sdk/react

The Uniform SDK for React provides two complementary packages for building personalized, composable experiences in any React application:

- **`@uniformdev/canvas-react`** -- Renders Uniform Canvas compositions as React component trees with support for slots, parameters, rich text, and visual editing.
- **`@uniformdev/context-react`** -- Provides React bindings for Uniform Context, enabling personalization, A/B testing, visitor scoring, and behavior tracking.

These packages work with any React setup -- Vite, Remix, Gatsby, a custom SSR server, or any other React-based architecture. For Next.js App Router projects, use the dedicated [`@uniformdev/next-app-router`](https://docs.uniform.app/docs/sdk/nextjs-app-router) package instead.

---

## Getting started

### Prerequisites

- React 18+
- Node.js 18+
- A Uniform project with compositions and component types configured

### Starter projects

Two official starter projects are available on GitHub to help you get up and running quickly:

| Starter | Description | Architecture |
| --- | --- | --- |
| [react-starter-cra](https://github.com/uniformdev/examples/tree/main/examples/react-starter-cra) | Context-only personalization with React Router. No Canvas compositions -- ideal for adding personalization to an existing React SPA. | Create React App, client-side only |
| [react-vite-ssr](https://github.com/uniformdev/examples/tree/main/examples/react-vite-ssr) | Full Canvas + Context integration with server-side rendering, including visual editor preview setup. | Vite + Express SSR with hydration |

Both starters include sample content that you can push to your own Uniform project with `npm run uniform:push`.

### Install packages

```bash
npm install @uniformdev/canvas-react@latest @uniformdev/context-react@latest @uniformdev/canvas@latest @uniformdev/context@latest
```

| Package | Purpose |
| --- | --- |
| `@uniformdev/canvas-react` | React components for rendering Canvas compositions |
| `@uniformdev/context-react` | React provider and hooks for personalization and testing |
| `@uniformdev/canvas` | Core Canvas client, types, and utilities |
| `@uniformdev/context` | Core Context engine for scoring and personalization |

### Peer dependencies

Both React packages require `react` and `react-dom` as peer dependencies. If you use client-side visibility rules via the `useClientConditionsComposition` hook, you also need `immer >= 10`:

```bash
npm install immer
```

> **Note:**
>
> `immer` is only required if you use `useClientConditionsComposition`. Most projects do not need it.

---

## Canvas

Canvas is Uniform's visual composition system. The `@uniformdev/canvas-react` package provides React components to render Canvas compositions -- turning the JSON composition tree from the Uniform API into a rendered React component tree.

### Core concepts

A Canvas **composition** is a tree structure where:

- The **root** is a composition instance (e.g., a "Page" component)
- Each component can have **slots** -- named locations where child components are placed
- Each component has **parameters** -- typed values (text, rich text, images, links, etc.) configured in the Uniform dashboard
- **Personalization** and **A/B test** containers are special system components that wrap variants

The React SDK walks this tree and renders each node using a **component resolver** -- a function you write that maps Uniform component types to your React components.

### Fetching compositions

Before rendering, you need to fetch the composition data from the Uniform API. Use `RouteClient` from `@uniformdev/canvas`:

```ts
// uniform/api.ts
import { RouteClient } from "@uniformdev/canvas";

const routeClient = new RouteClient({
  projectId: process.env.UNIFORM_PROJECT_ID,
  apiKey: process.env.UNIFORM_API_KEY,
});

export async function getComposition(path: string) {
  const response = await routeClient.getRoute({ path });

  if (response.type === "composition") {
    return response.compositionApiResponse.composition;
  }

  return null;
}
```

> **Note:**
>
> You can find your project ID and API key under **Security > Service Accounts** in your Uniform project. See the [API access guide](https://docs.uniform.app/docs/guides/api-access) for details.

### Rendering a composition

The `UniformComposition` component is the entry point for rendering a composition tree. It takes the composition data and a component resolver, and renders the full component tree:

```tsx
import { UniformComposition } from "@uniformdev/canvas-react";
import { UniformContext } from "@uniformdev/context-react";
import { Context } from "@uniformdev/context";
import { resolveRenderer } from "./resolveRenderer";
import { manifest } from "./uniform/manifest";

// Create the Context instance (typically done once at app level)
const context = new Context({
  manifest,
  defaultConsent: true,
});

function App({ composition }) {
  return (
    <UniformContext context={context}>
      <UniformComposition
        data={composition}
        resolveRenderer={resolveRenderer}
        behaviorTracking="onLoad"
      />
    </UniformContext>
  );
}
```

#### UniformComposition props

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `data` | `RootComponentInstance` | Yes | The composition data from the Uniform API |
| `resolveRenderer` | `RenderComponentResolver` | Yes | Function that maps component types to React components |
| `behaviorTracking` | `'onLoad' \| 'onView'` | No | When to track enrichment tags. `'onView'` uses IntersectionObserver (default), `'onLoad'` tracks immediately on render |
| `contextualEditingEnhancer` | `(message) => RootComponentInstance` | No | Enhancer for contextual editing updates |

### Component resolution

The component resolver is a function that receives a `ComponentInstance` and returns the React component to render. This is how you map Uniform component types to your React components:

```ts
// resolveRenderer.ts
import type { ComponentInstance } from "@uniformdev/canvas";
import { DefaultNotImplementedComponent } from "@uniformdev/canvas-react";
import Page from "./components/Page";
import Hero from "./components/Hero";
import Card from "./components/Card";

export function resolveRenderer(component: ComponentInstance) {
  switch (component.type) {
    case "page":
      return Page;
    case "hero":
      return Hero;
    case "card":
      return Card;
    default:
      return DefaultNotImplementedComponent;
  }
}
```

> **Tip:**
>
> Always return a fallback component (such as `DefaultNotImplementedComponent`) for unknown types. This prevents render errors and shows a helpful message in development that includes the missing component type name and a code snippet to help you register it.

#### Alternative: Component store registration

Instead of a switch statement, you can use the component store pattern to register components declaratively:

```ts
// components/index.ts
import { registerUniformComponent } from "@uniformdev/canvas-react";
import Hero from "./Hero";
import Card from "./Card";

registerUniformComponent({ type: "hero", component: Hero });
registerUniformComponent({ type: "card", component: Card });
```

Then use `componentStoreResolver` as your resolver:

```tsx
import { componentStoreResolver } from "@uniformdev/canvas-react";
import "./components"; // triggers registration

<UniformComposition
  data={composition}
  resolveRenderer={componentStoreResolver}
/>
```

The store supports variant-specific registration:

```ts
registerUniformComponent({
  type: "hero",
  variantId: "dark",
  component: HeroDark,
});
```

Resolution priority: exact type + variant match, then type-only match, then the default fallback.

### ComponentProps

Every component rendered by Canvas receives `ComponentProps`. This type gives you access to the raw component data:

```ts
import type { ComponentProps } from "@uniformdev/canvas-react";

type HeroProps = ComponentProps<{
  title: string;
  description?: string;
  buttonText?: string;
}>;
```

The `ComponentProps<T>` type extends your custom props with:

| Property | Type | Description |
| --- | --- | --- |
| `component` | `ComponentInstance` | The raw Canvas component instance with all metadata, slots, and parameters |

Parameter values are automatically extracted from `component.parameters[id].value` and flattened onto the component props by the SDK's internal `convertComponentToProps` helper. So if your Canvas component has a parameter named `title` with value `"Hello"`, your component receives `{ title: "Hello", component: { ... } }`.

### UniformSlot

`UniformSlot` renders child components placed in a named slot:

```tsx
import { UniformSlot } from "@uniformdev/canvas-react";
import type { ComponentProps } from "@uniformdev/canvas-react";

const Page = ({ component }: ComponentProps) => {
  return (
    <div>
      <header>
        <UniformSlot name="header" />
      </header>
      <main>
        <UniformSlot name="content" />
      </main>
      <footer>
        <UniformSlot name="footer" />
      </footer>
    </div>
  );
};

export default Page;
```

#### UniformSlot props

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | Yes | The slot name as defined in the Uniform component type |
| `resolveRenderer` | `RenderComponentResolver` | No | Override the parent's component resolver for this slot's children |
| `wrapperComponent` | `React.ComponentType` | No | A React component that wraps each item in the slot |
| `emptyPlaceholder` | `React.ReactNode` | No | Content to render when the slot has no children |

#### Custom wrapper for slot items

Use `wrapperComponent` to wrap each child in the slot with custom markup:

```tsx
const SlotItemWrapper = ({ children }) => (
  <div className="slot-item border-b pb-4 mb-4">
    {children}
  </div>
);

<UniformSlot name="content" wrapperComponent={SlotItemWrapper} />
```

#### Empty slot placeholder

Show fallback content when a slot has no children:

```tsx
<UniformSlot
  name="sidebar"
  emptyPlaceholder={<p className="text-gray-400">No sidebar content</p>}
/>
```

### UniformText

`UniformText` renders text parameters with built-in support for inline editing in the Uniform visual editor:

```tsx
import { UniformText } from "@uniformdev/canvas-react";

const Hero = () => {
  return (
    <section>
      <UniformText
        parameterId="title"
        as="h1"
        className="text-4xl font-bold"
        placeholder="Enter hero title"
      />
      <UniformText
        parameterId="subtitle"
        as="p"
        className="text-lg text-gray-600"
        placeholder="Enter subtitle"
      />
    </section>
  );
};
```

#### UniformText props

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `parameterId` | `string` | Yes | The parameter ID as defined in the Uniform component type |
| `as` | `React.ElementType` | No | HTML element to render (default: `'span'`) |
| `placeholder` | `string \| ((parameter) => string)` | No | Placeholder text shown in the editor when the value is empty |
| `isMultiline` | `boolean` | No | Enable line break support (default: `false`) |
| `render` | `(value: string \| undefined) => React.ReactNode` | No | Custom render function for transforming the value |

#### Custom rendering

Use the `render` prop to transform the parameter value before display:

```tsx
<UniformText
  parameterId="price"
  render={(value) => value ? `$${parseFloat(value).toFixed(2)}` : null}
/>
```

> **Note:**
>
> `UniformText` reads the parameter value from the nearest `UniformComponent` context. It must be used inside a component rendered by `UniformComposition` or `UniformSlot`.

### UniformRichText

`UniformRichText` renders rich text parameters with full formatting support including headings, lists, links, images, tables, and text formatting:

```tsx
import { UniformRichText } from "@uniformdev/canvas-react";

const Article = () => {
  return (
    <article>
      <UniformRichText
        parameterId="body"
        className="prose max-w-none"
        placeholder="Write your article content"
      />
    </article>
  );
};
```

#### UniformRichText props

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `parameterId` | `string` | Yes | The parameter ID for the rich text field |
| `as` | `React.ElementType \| null` | No | Wrapper element (default: `'div'`). Set to `null` for no wrapper |
| `placeholder` | `string \| ((parameter) => string)` | No | Placeholder shown in the editor when empty |
| `resolveRichTextRenderer` | `RenderRichTextComponentResolver` | No | Custom renderer for rich text nodes |

#### Custom rich text node rendering

Override how specific rich text nodes are rendered:

```tsx
import { linkParamValueToAnchorProps } from "@uniformdev/canvas-react";
import { isRichTextNodeType, type RichTextNode } from "@uniformdev/richtext";

function customRichTextRenderer(node: RichTextNode) {
  if (node.type === "link") {
    return ({ node, children }) => {
      // Narrow to a link node so `node.link` is available under strict TS
      if (!isRichTextNodeType(node, "link")) return null;
      return (
        <a
          {...linkParamValueToAnchorProps(node.link)}
          className="text-blue-600 hover:underline"
        >
          {children}
        </a>
      );
    };
  }
  // Return null/undefined to use default rendering
  return null;
}

<UniformRichText
  parameterId="body"
  resolveRichTextRenderer={customRichTextRenderer}
/>
```

Link nodes carry the link value on `node.link` (`{ type, path, nodeId?, projectMapId?, attributes? }`). The `linkParamValueToAnchorProps` helper from `@uniformdev/canvas-react` converts it to React anchor props: it derives the `href` and includes any [custom link attributes](https://docs.uniform.app/docs/guides/models/components/parameters#link) set by the editor (such as `target`, `rel`, or `data-*`), applying the built-in safeguards (security filtering, automatic `rel="noopener noreferrer"`, and `class` to `className` translation) described in [Link Parameter Utilities](https://docs.uniform.app/docs/sdk/parameter-utilities/link-parameters). The default link renderer applies the same behavior. Outside React (Vue, Nuxt, or HTML string output), use `linkParamValueToHtmlAttributes` from `@uniformdev/richtext`, which returns a plain attribute record.

Note that a custom `className` prop as in the example above overrides any `class` attribute set on the link by the editor; merge the two values if you need both.

The SDK includes built-in renderers for all standard rich text node types: paragraphs, headings (h1-h6), lists, list items, links, images/videos/audio assets, tables, line breaks, tabs, and formatted text (bold, italic, underline, strikethrough, code, subscript, superscript).

### Accessing raw parameter values

When you need parameter values directly (for non-visual use like URLs, booleans, or conditional logic), access them from the `component` prop:

```tsx
import type { ComponentProps } from "@uniformdev/canvas-react";

type CardProps = ComponentProps<{
  title: string;
  linkUrl?: string;
  isExternal?: boolean;
}>;

const Card = ({ title, linkUrl, isExternal, component }: CardProps) => {
  const Tag = linkUrl ? "a" : "div";
  const linkProps = linkUrl
    ? { href: linkUrl, target: isExternal ? "_blank" : undefined }
    : {};

  return (
    <Tag {...linkProps} className="card">
      <h3>{title}</h3>
    </Tag>
  );
};
```

### getParameterAttributes helper

For advanced inline editing scenarios, `getParameterAttributes` returns the data attributes needed to mark an element as editable by the Uniform visual editor:

```tsx
import { getParameterAttributes } from "@uniformdev/canvas-react";

const CustomText = ({ component }) => {
  const attrs = getParameterAttributes({
    component,
    id: "title",
    isMultiline: false,
  });

  return (
    <h1 {...attrs}>
      {component.parameters?.title?.value || "Untitled"}
    </h1>
  );
};
```

This is useful when you cannot use `UniformText` but still want inline editing support.

---

### Hooks

#### useUniformCurrentComposition

Returns the data and configuration from the nearest `UniformComposition` ancestor:

```tsx
import { useUniformCurrentComposition } from "@uniformdev/canvas-react";

const Breadcrumb = () => {
  const { data, isContextualEditing, matchedRoute } =
    useUniformCurrentComposition();

  return (
    <nav>
      <span>Current route: {matchedRoute}</span>
      {isContextualEditing && <span> (editing)</span>}
    </nav>
  );
};
```

**Return value:**

| Property | Type | Description |
| --- | --- | --- |
| `data` | `RootComponentInstance \| undefined` | The full composition data |
| `isContextualEditing` | `boolean` | Whether the composition is being edited in the Uniform visual editor |
| `matchedRoute` | `string \| undefined` | The matched route path |
| `dynamicInputs` | `Record<string, any> \| undefined` | Dynamic input values from route parameters |
| `resolveRenderer` | `RenderComponentResolver \| undefined` | The active component resolver |
| `behaviorTracking` | `'onLoad' \| 'onView' \| undefined` | The behavior tracking mode |

#### useUniformCurrentComponent

Returns the data from the nearest `UniformComponent` ancestor (the currently rendering component):

```tsx
import { useUniformCurrentComponent } from "@uniformdev/canvas-react";

const DebugInfo = () => {
  const { data } = useUniformCurrentComponent();

  if (!data) return null;

  return (
    <pre className="text-xs bg-gray-100 p-2 mt-2">
      Type: {data.type}
      {"\n"}Parameters: {JSON.stringify(data.parameters, null, 2)}
    </pre>
  );
};
```

#### useUniformContextualEditing

Enables contextual editing -- the two-way communication between your React app and the Uniform visual editor. When a content author edits a composition in the Uniform dashboard, this hook receives live updates and re-renders the composition in real time:

```tsx
import {
  UniformComposition,
  useUniformContextualEditing,
} from "@uniformdev/canvas-react";

function PreviewPage({ initialComposition }) {
  const { composition, isContextualEditing } = useUniformContextualEditing({
    initialCompositionValue: initialComposition,
    enhance: async (composition) => {
      // Optional: run enhancers on the composition before rendering
      return composition;
    },
  });

  return (
    <UniformComposition
      data={composition}
      resolveRenderer={resolveRenderer}
    />
  );
}
```

| Property | Type | Description |
| --- | --- | --- |
| `composition` | `RootComponentInstance` | The current composition data (updates live during editing) |
| `isContextualEditing` | `boolean` | `true` when the Uniform editor is active |

> **Note:**
>
> Contextual editing is automatically set up when you use `UniformComposition` with the `contextualEditingEnhancer` prop. Use `useUniformContextualEditing` directly only when you need full control over the editing lifecycle.

#### useUniformContextualEditingState

Provides information about the current contextual editing state, such as which component is selected in the editor:

```tsx
import { useUniformContextualEditingState } from "@uniformdev/canvas-react";

const ComponentHighlight = ({ children }) => {
  const { isContextualEditing, selectedComponentReference, previewMode } =
    useUniformContextualEditingState();

  const isSelected =
    selectedComponentReference?.componentId === component._id;

  return (
    <div className={isSelected ? "ring-2 ring-blue-500" : ""}>
      {children}
      {previewMode === "editor" && (
        <span className="text-xs text-gray-400">Editor mode</span>
      )}
    </div>
  );
};
```

| Property | Type | Description |
| --- | --- | --- |
| `isContextualEditing` | `boolean` | Whether contextual editing is active |
| `selectedComponentReference` | `object \| undefined` | Reference to the currently selected component in the editor |
| `previewMode` | `'editor' \| 'preview' \| undefined` | The current preview mode |

#### useClientConditionsComposition

Evaluates client-side visibility rules on a composition and returns a filtered composition with invisible components removed:

```tsx
import { useClientConditionsComposition } from "@uniformdev/canvas-react";

function PageWithVisibility({ composition }) {
  // Evaluates quirk-based visibility rules on the client
  const filteredComposition = useClientConditionsComposition(composition);

  return (
    <UniformComposition
      data={filteredComposition}
      resolveRenderer={resolveRenderer}
    />
  );
}
```

This hook uses `immer` internally for immutable updates and reads the current visitor's quirks from the Uniform Context to evaluate visibility conditions.

---

### Visual editing

The Uniform visual editor allows content authors to edit compositions directly in the browser. The React SDK supports this with:

1. **Contextual editing** -- Live preview updates when editing in the Uniform dashboard
2. **Inline editing** -- Direct text editing within the rendered page (via `UniformText` and `UniformRichText`)
3. **Component selection** -- Click-to-select components in the visual editor

To enable contextual editing, pass the `contextualEditingEnhancer` prop to `UniformComposition`:

```tsx
<UniformComposition
  data={composition}
  resolveRenderer={resolveRenderer}
  contextualEditingEnhancer={async (composition) => {
    // Optional: apply data enhancers or transformations
    return composition;
  }}
/>
```

The SDK automatically:

- Detects when the page is loaded inside the Uniform visual editor
- Sets up a message channel for live composition updates
- Adds data attributes to components and parameters for editor interaction
- Provides the `isContextualEditing` flag via `useUniformCurrentComposition`

### Setting up the visual editor preview

To enable the Uniform visual editor (contextual editing) with a React app, your server must handle two special requests from the Uniform dashboard:

1. **Config check** -- The editor pings your preview URL to check capabilities (e.g., whether a playground route exists).
2. **Preview route** -- When a content author opens the visual editor, the dashboard loads your app inside an iframe. On preview requests, your server should skip fetching composition data and instead return a page with an empty composition stub -- the editor sends the actual composition data over a `postMessage` channel.

Here is a complete Express server setup demonstrating both (from the [react-vite-ssr](https://github.com/uniformdev/examples/tree/main/examples/react-vite-ssr) starter):

```js
// server.js
import express from "express";
import cors from "cors";
import {
  isAllowedReferrer,
  EMPTY_COMPOSITION,
  IN_CONTEXT_EDITOR_CONFIG_CHECK_QUERY_STRING_PARAM,
} from "@uniformdev/canvas";
import { getComposition } from "./src/uniform/api.js";

const app = express();

// CORS is required so the Uniform dashboard can load your app in an iframe
app.use(cors());

app.use("*", async (req, res) => {
  // 1. Config check: the editor pings this to detect capabilities
  const isConfigCheck =
    req.query[IN_CONTEXT_EDITOR_CONFIG_CHECK_QUERY_STRING_PARAM] === "true";

  if (isConfigCheck) {
    res.json({ hasPlayground: true });
    return;
  }

  // 2. Preview mode: when loaded inside the Uniform visual editor
  let composition;

  if (
    req.params[0] === "/api/preview" &&
    isAllowedReferrer(req.headers.referer)
  ) {
    // No need to fetch -- the editor sends composition data via postMessage.
    // Use the empty stub so the page can render and establish the channel.
    composition = EMPTY_COMPOSITION;
  } else {
    // Normal request: fetch the composition from Uniform
    composition = await getComposition(req.params[0]);
  }

  // Server-render the app with the composition data
  const rendered = await render({ composition });
  const html = template
    .replace("<!--app-html-->", rendered.html ?? "");
  res.status(200).set({ "Content-Type": "text/html" }).send(html);
});
```

> **Note:**
>
> `isAllowedReferrer` checks that the request originates from the Uniform dashboard domain. `EMPTY_COMPOSITION` is a minimal valid composition stub that lets the page render before the editor injects the real data. Both are exported from `@uniformdev/canvas`.

#### Configuring the preview URL in Uniform

After setting up your server, configure the preview URL in your Uniform project so the visual editor knows where to load your app:

1. Go to **Settings > Canvas Settings > Preview** in your Uniform project.
2. Add a preview URL pointing to your preview route, for example: `http://localhost:5173/api/preview`.
3. Optionally configure preview viewports (Desktop, Tablet, Mobile) for responsive previewing.

The [react-vite-ssr](https://github.com/uniformdev/examples/tree/main/examples/react-vite-ssr) starter includes a pre-configured preview URL and viewport definitions in the `uniform-data/` folder that are pushed to your project with `npm run uniform:push`.

### UniformPlayground

`UniformPlayground` is a special component for previewing individual components and patterns in the Uniform dashboard. It renders with an empty composition and supports contextual editing:

```tsx
import { UniformPlayground } from "@uniformdev/canvas-react";

function PlaygroundPage() {
  return (
    <UniformPlayground resolveRenderer={resolveRenderer} />
  );
}
```

---

## Context

Context is Uniform's personalization and optimization engine. The `@uniformdev/context-react` package provides React components and hooks that integrate the Context engine into your React application, enabling personalization, A/B testing, visitor scoring, and behavior tracking.

### Using Context without Canvas

You don't need Canvas compositions to use Uniform personalization. The `@uniformdev/context-react` package works standalone in any React app -- you can add personalization, A/B testing, and visitor scoring to an existing React application without adopting Canvas or changing your rendering architecture.

The [react-starter-cra](https://github.com/uniformdev/examples/tree/main/examples/react-starter-cra) starter demonstrates this approach with a client-side React app that uses React Router for page navigation and Uniform Context for personalization. No Canvas compositions, no `@uniformdev/canvas` packages -- just Context.

```tsx
// App.tsx -- Context-only setup with React Router
import { UniformContext, useUniformContext } from "@uniformdev/context-react";
import { createUniformContext } from "./uniform/uniformContext";
import { Routes, Route, useLocation } from "react-router-dom";
import { useEffect } from "react";
import { parse } from "cookie";

const clientContext = createUniformContext();

function App() {
  return (
    <UniformContext context={clientContext}>
      <AppRoutes />
    </UniformContext>
  );
}

function AppRoutes() {
  const { context } = useUniformContext();
  const location = useLocation();

  // Update Context on every route change with current URL and cookies
  useEffect(() => {
    context.update({
      url: new URL(
        `${document.location.protocol}//${document.location.host}${location.pathname}${location.search}`
      ),
      cookies: parse(document.cookie ?? ""),
    });
  }, [location, context]);

  return (
    <Routes>
      <Route path="/" element={<HomePage />} />
      <Route path="/developers" element={<DevelopersPage />} />
      <Route path="/marketers" element={<MarketersPage />} />
    </Routes>
  );
}
```

In this pattern, you track enrichments programmatically inside your page components:

```tsx
// DevelopersPage.tsx
import { useUniformContext } from "@uniformdev/context-react";
import { useEffect } from "react";

export default function DevelopersPage() {
  const { context } = useUniformContext();

  useEffect(() => {
    context.update({
      enrichments: [
        {
          cat: "audience",
          key: "dev",
          str: 10,
        },
      ],
    });
  }, [context]);

  return <h2>For Developers</h2>;
}
```

This approach is ideal when you want to:

- Add personalization to an existing React SPA without a CMS
- Use Uniform signals and scoring with your own component architecture
- Implement programmatic enrichment tracking based on user navigation patterns

### Setting up UniformContext

The `UniformContext` provider wraps your application and provides the Context instance to all child components:

```tsx
import { Context, ManifestV2 } from "@uniformdev/context";
import { UniformContext } from "@uniformdev/context-react";

// The manifest defines your signals, enrichments, quirks, and tests.
// In production, fetch this from the Uniform API or embed it at build time.
const manifest: ManifestV2 = {
  project: {
    pz: {
      sig: {
        // Signal definitions
      },
    },
    test: {
      // A/B test definitions
    },
  },
};

const context = new Context({
  manifest,
  defaultConsent: true,
});

function App({ children }) {
  return (
    <UniformContext context={context}>
      {children}
    </UniformContext>
  );
}
```

#### Persisting visitor data with CookieTransitionDataStore

By default, visitor scores and quirks are stored in memory and lost on page reload. Use `CookieTransitionDataStore` to persist visitor data across sessions via cookies:

```tsx
import {
  Context,
  CookieTransitionDataStore,
} from "@uniformdev/context";

const context = new Context({
  manifest,
  defaultConsent: true,
  transitionStore: new CookieTransitionDataStore({
    // For SSR, pass the cookie value from the server request headers:
    serverCookieValue: serverCookieValue ?? "",
  }),
});
```

> **Tip:**
>
> For SSR apps, read the `ufvd` cookie from the incoming request headers and pass it as `serverCookieValue`. This ensures the server-rendered HTML reflects the visitor's existing scores and quirks.

#### Enabling debug logging

Use `enableDebugConsoleLogDrain` during development to log all Context activity (signal evaluations, score changes, enrichment tracking) to the browser console:

```tsx
import {
  Context,
  enableContextDevTools,
  enableDebugConsoleLogDrain,
} from "@uniformdev/context";

const context = new Context({
  manifest,
  defaultConsent: true,
  plugins: [
    enableContextDevTools(),
    enableDebugConsoleLogDrain("debug"),
  ],
});
```

#### UniformContext props

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `context` | `Context` | Yes | A configured Uniform Context instance |
| `outputType` | `'standard' \| 'edge'` | No | Rendering mode (default: `'standard'`). Use `'edge'` for edge-side personalization |
| `trackRouteOnRender` | `boolean` | No | Whether to track route changes on render (default: `true`) |
| `includeTransferState` | `'always' \| 'never' \| 'server-only'` | No | When to include server-to-client state transfer for SSR hydration (default: `'server-only'`) |

#### Transfer state for SSR

When server-rendering, the `UniformContext` automatically injects a `<script>` tag containing the serialized context state. The client picks this up during hydration so that the visitor's scores and quirks are preserved without a flash of unpersonalized content:

```tsx
// On the server, this injects transfer state automatically
<UniformContext context={context} includeTransferState="always">
  <App />
</UniformContext>
```

### Creating the manifest

The personalization manifest defines your project's signals, enrichments, quirks, and A/B tests. There are several ways to create it:

**Option 1: Hardcode for development**

```ts
import { ManifestV2 } from "@uniformdev/context";

export const manifest: ManifestV2 = {
  project: {
    pz: {
      sig: {
        launchCampaign: {
          str: 50,
          cap: 100,
          dur: "p",
          crit: {
            op: "&",
            type: "G",
            clauses: [
              {
                type: "QS",
                match: { cs: false, op: "=", rhs: "launch" },
                queryName: "utm_campaign",
              },
            ],
          },
        },
      },
    },
    test: {},
  },
};
```

**Option 2: Download via CLI (recommended for production)**

```bash
npx uniform context manifest download --output ./src/uniform/manifest.json
```

Then import it:

```ts
import manifest from "./uniform/manifest.json";
```

**Option 3: Fetch at runtime**

```ts
import { ManifestClient } from "@uniformdev/context";

const manifestClient = new ManifestClient({
  apiKey: process.env.UNIFORM_API_KEY,
  projectId: process.env.UNIFORM_PROJECT_ID,
});

const manifest = await manifestClient.get();
```

> **Warning:**
>
> After modifying signals, enrichments, or quirks in the Uniform dashboard, you must publish the manifest for changes to take effect:
>
> ```bash
> npx uniform context manifest publish
> ```

---

### Personalization

The `Personalize` component selects and renders content variants based on the visitor's scores, quirks, and other classification data.

#### How it works

1. You define multiple **variations** of a content component, each tagged with personalization criteria (enrichment tags/scores).
2. The `Personalize` component calls `context.personalize()` to evaluate the visitor's current scores against each variation's criteria.
3. The winning variation(s) are rendered. If no variation matches, the default (first) variation is shown.

```tsx
import { Personalize } from "@uniformdev/context-react";

const variations = [
  { id: "default", title: "Welcome!", pz: {} },
  {
    id: "tech-lover",
    title: "Welcome, tech enthusiast!",
    pz: { crit: [{ l: "tech", op: ">=", rhs: 50 }] },
  },
  {
    id: "marketer",
    title: "Welcome, marketing pro!",
    pz: { crit: [{ l: "marketing", op: ">=", rhs: 50 }] },
  },
];

function PersonalizedHero() {
  return (
    <Personalize
      name="heroPersonalization"
      variations={variations}
      component={({ title, personalizationResult }) => (
        <section>
          <h1>{title}</h1>
          {personalizationResult.personalizationOccurred && (
            <span className="text-sm text-green-600">
              Personalized for you
            </span>
          )}
        </section>
      )}
    />
  );
}
```

#### Personalize props

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | Yes | Unique name for this placement (used in analytics) |
| `variations` | `PersonalizedVariant[]` | Yes | Array of possible variations to select from |
| `component` | `React.ComponentType` | Yes | Component to render for the selected variation |
| `count` | `number` | No | Number of variations to select (default: `1`) |
| `algorithm` | `string` | No | Personalization algorithm to use |
| `wrapperComponent` | `React.ComponentType` | No | Wrapper component that receives `personalizationOccurred` flag |
| `compositionMetadata` | `CompositionMetadata` | No | Metadata for analytics tracking |

The rendered component receives all variation properties plus a `personalizationResult` object:

```ts
{
  variation: PersonalizedVariant;     // The selected variation data
  personalizationOccurred: boolean;   // Whether personalization actually changed the output
}
```

#### Wrapper component

Use `wrapperComponent` to add conditional styling or tracking based on whether personalization occurred:

```tsx
const PersonalizationWrapper = ({ children, personalizationOccurred }) => (
  <div
    className={personalizationOccurred ? "border-l-4 border-blue-500" : ""}
    data-personalized={personalizationOccurred}
  >
    {children}
  </div>
);

<Personalize
  name="hero"
  variations={variations}
  component={HeroVariant}
  wrapperComponent={PersonalizationWrapper}
/>
```

---

### A/B testing

The `Test` component implements A/B testing by selecting a single variant based on test distribution percentages:

```tsx
import { Test } from "@uniformdev/context-react";

const variations = [
  { id: "control", testDistribution: 50, heading: "Sign up now" },
  { id: "variant-a", testDistribution: 50, heading: "Get started free" },
];

function ABTestHero() {
  return (
    <Test
      name="signupHeadingTest"
      variations={variations}
      component={({ heading }) => <h1>{heading}</h1>}
    />
  );
}
```

#### Test props

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | Yes | Unique test name |
| `variations` | `TestVariant[]` | Yes | Array of test variations, each with a `testDistribution` percentage |
| `component` | `React.ComponentType` | Yes | Component to render for the selected variation |
| `loadingMode` | `'default' \| 'none' \| React.ComponentType` | No | How to handle loading state |
| `compositionMetadata` | `CompositionMetadata` | No | Metadata for analytics tracking |

> **Note:**
>
> **Key difference from personalization:** A/B tests use deterministic distribution percentages (`testDistribution`) to split traffic. Personalization uses visitor scores and quirks to algorithmically select the best variant.

---

### Behavior tracking

Track visitor interactions to build up enrichment scores that drive personalization. The SDK provides two tracking components:

#### Track (viewport-based)

`Track` tracks visitor behavior when content enters the viewport using `IntersectionObserver`:

```tsx
import { Track } from "@uniformdev/context-react";

const Article = ({ enrichments, title, children }) => {
  return (
    <Track behavior={enrichments}>
      <article>
        <h2>{title}</h2>
        {children}
      </article>
    </Track>
  );
};
```

#### Track props

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `behavior` | `EnrichmentData \| EnrichmentData[]` | Yes | Enrichment data to track |
| `children` | `React.ReactNode` | Yes | Content to track visibility of |
| `tagName` | `keyof JSX.IntrinsicElements` | No | Wrapper element (default: `'div'`) |
| `threshold` | `number \| number[]` | No | Visibility threshold (default: `0.5` = 50% visible) |
| `disableVisibilityTrigger` | `boolean` | No | Disable viewport tracking (falls back to immediate) |

#### TrackFragment (page load-based)

`TrackFragment` tracks immediately on page load / route change without adding a wrapper element:

```tsx
import { TrackFragment } from "@uniformdev/context-react";

const BlogPost = ({ enrichments, children }) => {
  return (
    <>
      <TrackFragment behavior={enrichments} />
      {children}
    </>
  );
};
```

Both tracking components:

- Track once per URL change per component instance (no duplicate tracking)
- Do not track when inside a `Personalize` component (prevents double-counting)
- Call `context.update({ enrichments })` to update the visitor's scores

---

### Hooks

#### useUniformContext

Accesses the Uniform Context instance from the nearest `UniformContext` provider:

```tsx
import { useUniformContext } from "@uniformdev/context-react";

const ContextDebug = () => {
  const { context } = useUniformContext();

  const handleReset = () => {
    context.forget(true);
  };

  return <button onClick={handleReset}>Reset visitor data</button>;
};
```

By default, this hook throws an error if no `UniformContext` provider is found. To make it optional:

```tsx
const result = useUniformContext({ throwOnMissingProvider: false });
// result may be undefined if no provider exists
```

> **Warning:**
>
> The `context.scores` and `context.quirks` properties accessed directly from the Context instance are **not reactive** -- they don't trigger re-renders when they change. Use `useScores()` and `useQuirks()` for reactive access.

#### useScores

Provides reactive access to the visitor's current scores. The component re-renders automatically when scores change:

```tsx
import { useScores } from "@uniformdev/context-react";

const ScoreBoard = () => {
  const scores = useScores();

  return (
    <div>
      <h3>Your interests:</h3>
      <ul>
        {Object.entries(scores)
          .filter(([, score]) => score > 0)
          .sort(([, a], [, b]) => b - a)
          .map(([signal, score]) => (
            <li key={signal}>
              {signal}: {score}
            </li>
          ))}
      </ul>
    </div>
  );
};
```

Returns a `ScoreVector` -- an object mapping signal names to numeric score values.

#### useQuirks

Provides reactive access to the visitor's current quirks. Re-renders when quirks change:

```tsx
import { useQuirks } from "@uniformdev/context-react";

const LocationBanner = () => {
  const quirks = useQuirks();

  if (!quirks.country) return null;

  return (
    <div className="bg-blue-50 p-4 rounded">
      Welcome from {quirks.country}!
    </div>
  );
};
```

Returns a `Quirks` object -- key-value pairs representing visitor attributes.

#### Updating quirks programmatically

Use the Context instance to set quirks from user interactions:

```tsx
import { useUniformContext } from "@uniformdev/context-react";

const CountrySelector = () => {
  const { context } = useUniformContext();

  const setCountry = (country: string) => {
    context.update({
      quirks: { country },
    });
  };

  return (
    <select onChange={(e) => setCountry(e.target.value)}>
      <option value="">Select country</option>
      <option value="US">United States</option>
      <option value="CA">Canada</option>
      <option value="UK">United Kingdom</option>
    </select>
  );
};
```

#### useIsPersonalized

Determines if the current component is inside a `Personalize` component:

```tsx
import { useIsPersonalized } from "@uniformdev/context-react";

const ContentBlock = ({ children }) => {
  const isPersonalized = useIsPersonalized();

  return (
    <div data-personalized={isPersonalized}>
      {children}
      {isPersonalized && <span className="badge">Personalized</span>}
    </div>
  );
};
```

---

### Edge-side rendering

The Context React SDK supports edge-side personalization and testing. When `outputType` is set to `'edge'`, the `Personalize` and `Test` components emit all variations wrapped in special HTML tags. An edge worker (Cloudflare, Vercel, Netlify, Akamai) then selects the winning variant before delivering the page to the visitor:

```tsx
<UniformContext context={context} outputType="edge">
  <App />
</UniformContext>
```

In edge mode:

- `Personalize` renders all variations with metadata tags for edge selection
- `Test` renders all variations with test distribution metadata
- The edge worker evaluates scores/quirks and strips non-winning variants from the HTML

See the [edge-side personalization guide](https://docs.uniform.app/docs/guides/personalization/edge-side-personalization) for detailed setup instructions.

---

## Full project example

Here is a complete example showing how to wire up both Canvas and Context in a React + Vite SSR project.

### Project structure

```
your-project/
├── src/
│   ├── components/
│   │   ├── Page.tsx
│   │   ├── Hero.tsx
│   │   └── resolveRenderer.ts
│   ├── uniform/
│   │   ├── api.ts
│   │   └── manifest.ts
│   ├── App.tsx
│   ├── entry-client.tsx
│   └── entry-server.tsx
├── server.js
├── package.json
└── vite.config.ts
```

### App component

```tsx
// src/App.tsx
import { UniformComposition } from "@uniformdev/canvas-react";
import { UniformContext } from "@uniformdev/context-react";
import { Context } from "@uniformdev/context";
import { resolveRenderer } from "./components/resolveRenderer";
import { manifest } from "./uniform/manifest";
import type { RootComponentInstance } from "@uniformdev/canvas";

const context = new Context({
  manifest,
  defaultConsent: true,
});

type AppProps = {
  composition?: RootComponentInstance;
};

export default function App({ composition }: AppProps) {
  // On the client, prefer preloaded data from SSR
  let data = composition;
  if (typeof window !== "undefined" && (window as any).__UNIFORM_DATA__) {
    data = (window as any).__UNIFORM_DATA__;
  }

  if (!data) {
    return <div>No composition found</div>;
  }

  return (
    <UniformContext context={context}>
      <UniformComposition
        data={data}
        resolveRenderer={resolveRenderer}
        behaviorTracking="onLoad"
      />
      {/* Transfer composition data to client for hydration */}
      <script
        dangerouslySetInnerHTML={{
          __html: `window.__UNIFORM_DATA__ = ${JSON.stringify(data)};`,
        }}
      />
    </UniformContext>
  );
}
```

### Component resolver

```ts
// src/components/resolveRenderer.ts
import type { ComponentInstance } from "@uniformdev/canvas";
import { DefaultNotImplementedComponent } from "@uniformdev/canvas-react";
import Page from "./Page";
import Hero from "./Hero";

export function resolveRenderer(component: ComponentInstance) {
  switch (component.type) {
    case "page":
      return Page;
    case "hero":
      return Hero;
    default:
      return DefaultNotImplementedComponent;
  }
}
```

### Page component

```tsx
// src/components/Page.tsx
import { UniformSlot } from "@uniformdev/canvas-react";
import type { ComponentProps } from "@uniformdev/canvas-react";

const Page = ({ component }: ComponentProps) => {
  return (
    <div className="page">
      <UniformSlot name="content" />
    </div>
  );
};

export default Page;
```

### Hero component

```tsx
// src/components/Hero.tsx
import { UniformText, UniformRichText } from "@uniformdev/canvas-react";
import type { ComponentProps } from "@uniformdev/canvas-react";

type HeroProps = ComponentProps<{
  title: string;
  description?: string;
}>;

const Hero = ({ title, description, component }: HeroProps) => {
  return (
    <section className="hero">
      <UniformText
        parameterId="title"
        as="h1"
        placeholder="Hero title goes here"
      />
      <UniformRichText
        parameterId="description"
        placeholder="Hero description"
      />
    </section>
  );
};

export default Hero;
```

### API client

```ts
// src/uniform/api.ts
import { RouteClient } from "@uniformdev/canvas";

const routeClient = new RouteClient({
  projectId: process.env.UNIFORM_PROJECT_ID!,
  apiKey: process.env.UNIFORM_API_KEY!,
});

export async function getComposition(path: string) {
  const response = await routeClient.getRoute({ path: path || "/" });

  if (response.type === "composition") {
    return response.compositionApiResponse.composition;
  }

  return null;
}
```

### Server entry

```tsx
// src/entry-server.tsx
import React from "react";
import ReactDOMServer from "react-dom/server";
import App from "./App";
import type { RootComponentInstance } from "@uniformdev/canvas";

export async function render({
  composition,
}: {
  composition: RootComponentInstance;
}) {
  const html = ReactDOMServer.renderToString(
    <React.StrictMode>
      <App composition={composition} />
    </React.StrictMode>
  );
  return { html };
}
```

### Client entry

```tsx
// src/entry-client.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";

ReactDOM.hydrateRoot(
  document.getElementById("root")!,
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
```

---

## Import reference

### @uniformdev/canvas-react

| Export | Description |
| --- | --- |
| `UniformComposition` | Renders a full Canvas composition tree |
| `UniformSlot` | Renders child components from a named slot |
| `UniformText` | Renders text parameters with inline editing support |
| `UniformRichText` | Renders rich text parameters |
| `UniformPlayground` | Playground for visual editing preview |
| `DefaultNotImplementedComponent` | Fallback for unmapped component types |
| `registerUniformComponent` | Registers a component in the component store |
| `componentStoreResolver` | Resolver function that uses the component store |
| `useUniformCurrentComposition` | Hook: access composition context |
| `useUniformCurrentComponent` | Hook: access current component context |
| `useUniformContextualEditing` | Hook: enable contextual editing |
| `useUniformContextualEditingState` | Hook: read contextual editing state |
| `convertComponentToProps` | Helper: convert component instance to flat props |
| `getParameterAttributes` | Helper: get inline editing data attributes |

### @uniformdev/context-react

| Export | Description |
| --- | --- |
| `UniformContext` | Provider component for the Uniform Context engine |
| `Personalize` | Renders personalized content based on visitor scores |
| `Test` | Renders A/B test variations based on distribution |
| `Track` | Tracks behavior when content enters the viewport |
| `TrackFragment` | Tracks behavior on page load (no wrapper element) |
| `useUniformContext` | Hook: access the Context instance |
| `useScores` | Hook: reactive visitor scores |
| `useQuirks` | Hook: reactive visitor quirks |
| `useIsPersonalized` | Hook: check if inside a Personalize component |

### @uniformdev/canvas

| Export | Description |
| --- | --- |
| `RouteClient` | Client for resolving routes and fetching compositions |
| `CanvasClient` | Client for direct composition API access |
| `RootComponentInstance` | Type for composition root data |
| `ComponentInstance` | Type for individual component data |
| `EMPTY_COMPOSITION` | Minimal composition stub for visual editor preview routes |
| `isAllowedReferrer` | Checks if a request originates from the Uniform dashboard |
| `IN_CONTEXT_EDITOR_CONFIG_CHECK_QUERY_STRING_PARAM` | Query string parameter name used by the editor config check |

### @uniformdev/context

| Export | Description |
| --- | --- |
| `Context` | The core personalization and scoring engine |
| `ManifestV2` | Type for the personalization manifest |
| `enableContextDevTools` | Plugin for enabling the Uniform DevTools browser extension |
| `enableDebugConsoleLogDrain` | Plugin for logging all Context activity to the browser console |
| `CookieTransitionDataStore` | Persists visitor scores and quirks across sessions via cookies |

---

## Recipes

### Recipe: Adding Uniform to an existing React + Vite project

1. Install packages:

```bash
npm install @uniformdev/canvas-react@latest @uniformdev/context-react@latest @uniformdev/canvas@latest @uniformdev/context@latest
```

2. Create environment variables (`.env`):

```bash
UNIFORM_PROJECT_ID=your-project-id
UNIFORM_API_KEY=your-api-key
```

3. Create the manifest file. Download it via CLI:

```bash
npx uniform context manifest download --output ./src/uniform/manifest.json
```

4. Create the API client (`src/uniform/api.ts`):

```ts
import { RouteClient } from "@uniformdev/canvas";

const routeClient = new RouteClient({
  projectId: import.meta.env.VITE_UNIFORM_PROJECT_ID,
  apiKey: import.meta.env.VITE_UNIFORM_API_KEY,
});

export async function getComposition(path: string) {
  const response = await routeClient.getRoute({ path: path || "/" });
  if (response.type === "composition") {
    return response.compositionApiResponse.composition;
  }
  return null;
}
```

5. Create the component resolver (`src/resolveRenderer.ts`):

```ts
import { DefaultNotImplementedComponent } from "@uniformdev/canvas-react";
// Import your components here

export function resolveRenderer(component) {
  switch (component.type) {
    // Map your Uniform component types to React components
    default:
      return DefaultNotImplementedComponent;
  }
}
```

6. Wrap your app with `UniformContext` and render compositions with `UniformComposition`:

```tsx
import { Context } from "@uniformdev/context";
import { UniformContext } from "@uniformdev/context-react";
import { UniformComposition } from "@uniformdev/canvas-react";
import manifest from "./uniform/manifest.json";
import { resolveRenderer } from "./resolveRenderer";

const context = new Context({ manifest, defaultConsent: true });

function App({ composition }) {
  return (
    <UniformContext context={context}>
      <UniformComposition
        data={composition}
        resolveRenderer={resolveRenderer}
      />
    </UniformContext>
  );
}
```

7. Add CLI scripts to `package.json`:

```json
{
  "scripts": {
    "uniform:push": "npx uniform sync push",
    "uniform:pull": "npx uniform sync pull",
    "uniform:publish": "npx uniform context manifest publish",
    "uniform:manifest": "npx uniform context manifest download --output ./src/uniform/manifest.json"
  }
}
```

### Recipe: Component with personalized slot content

```tsx
import { UniformSlot } from "@uniformdev/canvas-react";
import { useQuirks, useScores } from "@uniformdev/context-react";

const PersonalizedSection = ({ component }) => {
  const quirks = useQuirks();
  const scores = useScores();

  const greeting = quirks.country === "CA"
    ? "Welcome, eh!"
    : "Welcome!";

  const topSignal = Object.entries(scores)
    .sort(([, a], [, b]) => b - a)[0];

  return (
    <section>
      <h2>{greeting}</h2>
      {topSignal && (
        <p>We noticed you like {topSignal[0]} (score: {topSignal[1]})</p>
      )}
      <UniformSlot name="personalizedContent" />
    </section>
  );
};
```

### Recipe: Tracking user behavior for personalization

```tsx
import { Track, TrackFragment } from "@uniformdev/context-react";
import { UniformText } from "@uniformdev/canvas-react";

// Track when an article section scrolls into view
const ArticleSection = ({ enrichments, component }) => {
  return (
    <Track
      behavior={enrichments}
      threshold={0.5}
      tagName="section"
    >
      <UniformText parameterId="title" as="h2" />
      <UniformText parameterId="body" as="p" />
    </Track>
  );
};

// Track immediately on page load (no wrapper element)
const PageView = ({ enrichments, children }) => {
  return (
    <>
      <TrackFragment behavior={enrichments} />
      {children}
    </>
  );
};
```

### Recipe: Updating Context on route changes (React Router)

In a client-side SPA with React Router, Uniform Context needs to be told about route changes so signals based on URL patterns can evaluate correctly. Update Context in a `useEffect` that depends on the current location:

```tsx
import { useUniformContext } from "@uniformdev/context-react";
import { useLocation } from "react-router-dom";
import { parse } from "cookie";
import { useEffect } from "react";

function ContextRouteSync({ children }) {
  const { context } = useUniformContext();
  const location = useLocation();

  useEffect(() => {
    context.update({
      url: new URL(
        `${document.location.protocol}//${document.location.host}${location.pathname}${location.search}`
      ),
      cookies: parse(document.cookie ?? ""),
    });
  }, [location, context]);

  return <>{children}</>;
}
```

Place this component just below `UniformContext` in your tree so all child routes benefit from the updated Context state.

### Recipe: Displaying visitor scores and quirks for debugging

Build a debug panel that shows the current visitor's scores and quirks during development. This is useful for verifying that signals fire correctly and enrichments accumulate as expected:

```tsx
import { useUniformContext } from "@uniformdev/context-react";

function ContextDebugPanel() {
  const { context } = useUniformContext();

  const forgetMe = async () => {
    await context.forget(true);
    window.location.reload();
  };

  return (
    <div style={{ padding: 16, background: "#f5f5f5", fontSize: 12 }}>
      <h3>Context Debug</h3>
      <p>Quirks:</p>
      <pre>{JSON.stringify(context.quirks, null, 2)}</pre>
      <p>Scores:</p>
      <pre>{JSON.stringify(context.scores, null, 2)}</pre>
      <button onClick={forgetMe}>Reset visitor data</button>
    </div>
  );
}
```

> **Tip:**
>
> `context.forget(true)` clears all stored visitor data including scores, quirks, and consent state. The `true` argument also clears the cookie-based transition store if you are using `CookieTransitionDataStore`.

### Recipe: DevTools integration

Enable the Uniform Context DevTools browser extension for debugging:

```tsx
import { Context, enableContextDevTools } from "@uniformdev/context";
import { UniformContext } from "@uniformdev/context-react";
import manifest from "./uniform/manifest.json";

const context = new Context({
  manifest,
  defaultConsent: true,
  plugins: [enableContextDevTools()],
});

function App({ children }) {
  return (
    <UniformContext context={context}>
      {children}
    </UniformContext>
  );
}
```

The DevTools extension lets you inspect and modify visitor scores and quirks in real time during development.

---

## Best practices

1. **Always wrap with `UniformContext`** -- Place the `UniformContext` provider above `UniformComposition` in the component tree. Canvas components need Context for personalization, A/B testing, and behavior tracking.
2. **Create the `Context` instance once** -- Instantiate the `Context` object at the module level or in a top-level component, not inside render functions. Creating it in a render function resets visitor state on every render.
3. **Use `DefaultNotImplementedComponent` as fallback** -- Always return a fallback from your component resolver. It shows the component type name and a helpful code snippet during development.
4. **Mark parameter types as optional** -- Even required parameters can be `undefined` at runtime (e.g., when first adding a component to a composition). Always handle `undefined` values.
5. **Use `UniformText` and `UniformRichText` for visible text** -- These components provide inline editing support in the Uniform visual editor. Use raw parameter access only for non-visible values like URLs, booleans, or conditional logic.
6. **Hydrate composition data for SSR** -- When server-rendering, serialize the composition to a `<script>` tag and read it on the client to avoid re-fetching during hydration.
7. **Keep the manifest up to date** -- Run `npx uniform context manifest publish` after modifying signals, enrichments, or quirks. Then re-download or re-fetch the manifest.
8. **Use `useScores` and `useQuirks` for reactive data** -- Accessing `context.scores` directly does not trigger re-renders. Always use the hooks for values that affect the UI.
9. **Avoid tracking inside Personalize** -- The `Track` and `TrackFragment` components automatically skip tracking when inside a `Personalize` component to prevent double-counting.
10. **Leverage `immer` for visibility rules** -- The `useClientConditionsComposition` hook requires `immer >= 10` as a peer dependency. Install it if you use client-side visibility rules.
