# EmbedProvider advanced (React)

> Advanced EmbedProvider patterns for React: route visibility, delay policies, programmatic control, and production best practices.

URL: /embed/integration/embed-react-provider-advanced
Markdown: /embed/integration/embed-react-provider-advanced.md



# EmbedProvider — Basic to Advanced Guide for React [#embedprovider--basic-to-advanced-guide-for-react]

`EmbedProvider` is a React context provider that manages **where** and **when** the AI widget appears across your app. Instead of manually placing `<EmbedButton />` on every page, you wrap your app once and let the provider handle everything.

***

## How It Works (Internals Overview) [#how-it-works-internals-overview]

```
EmbedProvider
 ├── Detects current route (via usePathHook → currentPath prop → window.location fallback)
 ├── Checks if the route is in includeScreens
 ├── Applies delay logic (embedButtonDelayMs / group config)
 └── Renders <EmbedButton /> when conditions are met
```

* If `includeScreens` is **empty** → widget shows on **every route**
* If `includeScreens` has values → widget shows **only on those routes**
* Path detection priority: `currentPath` prop > `usePathHook` > `window.location`

***

## Level 1 — Show Widget on Every Page [#level-1--show-widget-on-every-page]

The simplest setup. No route filtering, no delays.

```tsx
// app/layout.tsx (Next.js) or your root component
"use client";

import { useInitialize, EmbedProvider } from '@revrag-ai/embed-react';
import '@revrag-ai/embed-react/dist/ai-assistant-widget.css';

export default function RootLayout({ children }) {
  useInitialize("your-api-key");

  return (
    <html lang="en">
      <body>
        <EmbedProvider>
          {children}
        </EmbedProvider>
      </body>
    </html>
  );
}
```

The widget appears on every route immediately. That's it.

***

## Level 2 — Show Widget Only on Specific Routes [#level-2--show-widget-only-on-specific-routes]

Use `includeScreens` to whitelist which routes show the widget.

### Exact Match (default) [#exact-match-default]

`/help` matches **only** `/help` — not `/help/faq`.

```tsx
<EmbedProvider
  usePathHook={useNextPathname}
  includeScreens={['/help', '/support', '/contact']}
  matchMode="exact"
>
  {children}
</EmbedProvider>
```

### Prefix Match [#prefix-match]

`/help` matches `/help`, `/help/faq`, `/help/contact`, etc.

```tsx
<EmbedProvider
  usePathHook={useNextPathname}
  includeScreens={['/help', '/dashboard']}
  matchMode="startsWith"
>
  {children}
</EmbedProvider>
```

### Injecting the Router Hook [#injecting-the-router-hook]

The provider needs to know the current path. Pass your router's hook so it updates on navigation.

**Next.js App Router:**

```tsx
import { usePathname } from 'next/navigation';

// Wrap in a named function — required because hooks must be
// called unconditionally and stably inside the provider
function useNextPathname() {
  return usePathname();
}

<EmbedProvider usePathHook={useNextPathname} includeScreens={['/help']} />
```

**React Router:**

```tsx
import { useLocation } from 'react-router-dom';

function useReactRouterPath() {
  return useLocation().pathname;
}

<EmbedProvider usePathHook={useReactRouterPath} includeScreens={['/help']} />
```

**Manual override (any framework):**

```tsx
// Pass the path directly — highest priority, overrides everything
<EmbedProvider currentPath={myCurrentPath} includeScreens={['/help']} />
```

***

## Level 3 — Delay the Widget Appearance [#level-3--delay-the-widget-appearance]

Show the widget after the user has been on a screen for a few seconds, so it doesn't feel intrusive.

```tsx
<EmbedProvider
  usePathHook={useNextPathname}
  includeScreens={['/pricing', '/checkout']}
  embedButtonDelayMs={4000}   // wait 4 seconds after arriving on the screen
>
  {children}
</EmbedProvider>
```

**What happens:**

1. User navigates to `/pricing`
2. Provider hides the widget and starts a 4-second timer
3. Timer fires → widget appears with animation
4. User navigates away → widget hides immediately, timer resets
5. User comes back to `/pricing` → 4-second timer starts again

***

## Level 4 — Customize the Button Position [#level-4--customize-the-button-position]

Override where the floating button sits on the screen.

```tsx
<EmbedProvider
  usePathHook={useNextPathname}
  embedButtonPosition={{ bottom: 80, right: 24 }}  // numbers = px
>
  {children}
</EmbedProvider>
```

You can also pass CSS strings:

```tsx
embedButtonPosition={{ bottom: '5rem', right: '1.5rem' }}
```

Pass any `EmbedButton` prop through `embedButtonProps`:

```tsx
<EmbedProvider
  usePathHook={useNextPathname}
  embedButtonProps={{
    positioning: 'fixed',
    bottomOffset: 60,       // offset for a 60px bottom navbar
    className: 'my-widget',
  }}
>
  {children}
</EmbedProvider>
```

***

## Level 5 — Group-Based Visibility [#level-5--group-based-visibility]

This is the advanced visibility engine. Use it when different sections of your app need different delay or continuity behavior.

### The Problem It Solves [#the-problem-it-solves]

Without groups, every navigation triggers the delay timer — so if a user moves between `/checkout` and `/payment` (both part of checkout), the widget keeps hiding and re-appearing. Groups prevent that.

### Core Concepts [#core-concepts]

**`continuity`** — controls what happens when navigating *within* a group:

| Value          | Behavior                                                                             |
| -------------- | ------------------------------------------------------------------------------------ |
| `"continuous"` | Widget stays visible — no re-animation when moving between screens in the same group |
| `"perScreen"`  | Widget re-applies delay on every screen, even within the group                       |

**`delayPolicy`** — controls *when* the delay fires:

| Value                 | Behavior                                                    |
| --------------------- | ----------------------------------------------------------- |
| `"perScreen"`         | Delay fires on every screen in the group                    |
| `"oncePerGroupEntry"` | Delay fires only the first time the user enters this group  |
| `"oncePerAppSession"` | Delay fires at most once per browser session for this group |

### Basic Groups Example [#basic-groups-example]

```tsx
import { EmbedProvider } from '@revrag-ai/embed-react';
import type { EmbedButtonVisibilityConfig } from '@revrag-ai/embed-react';

const visibilityConfig: EmbedButtonVisibilityConfig = {
  groups: [
    {
      id: 'checkout-flow',
      screens: ['/cart', '/checkout', '/payment', '/confirmation'],
      continuity: 'continuous',         // no re-animation between checkout steps
      delayMs: 2000,
      delayPolicy: 'oncePerGroupEntry', // delay only on first entry to checkout
    },
  ],
};

<EmbedProvider
  usePathHook={useNextPathname}
  embedButtonVisibilityConfig={visibilityConfig}
>
  {children}
</EmbedProvider>
```

**What happens:**

1. User is on `/home` → widget hidden (not in any group)
2. User goes to `/cart` → 2-second delay, then widget appears
3. User goes to `/checkout` → widget stays visible (same group, `continuous`)
4. User goes to `/payment` → widget stays visible (same group, `continuous`)
5. User leaves to `/home` → widget hidden
6. User comes back to `/cart` → **no delay** this time (`oncePerGroupEntry` — already triggered)

### Multiple Groups [#multiple-groups]

```tsx
const visibilityConfig: EmbedButtonVisibilityConfig = {
  defaultDelayMs: 1000,   // fallback delay for screens not in any group

  groups: [
    {
      id: 'onboarding',
      screens: ['/welcome', '/setup', '/profile-setup'],
      continuity: 'continuous',
      delayMs: 5000,
      delayPolicy: 'oncePerAppSession', // only delays once per browser session
    },
    {
      id: 'checkout',
      screens: ['/cart', '/checkout', '/payment'],
      continuity: 'continuous',
      delayMs: 2000,
      delayPolicy: 'oncePerGroupEntry',
    },
    {
      id: 'support',
      screens: ['/help', '/faq', '/contact'],
      continuity: 'perScreen',          // re-animate on every support page
      delayMs: 3000,
      delayPolicy: 'perScreen',
    },
  ],
};
```

### defaultDelayMs [#defaultdelayms]

Applies to any screen that is **included** (via `includeScreens`) but **not in any group**:

```tsx
<EmbedProvider
  usePathHook={useNextPathname}
  includeScreens={['/dashboard', '/settings', '/help']}
  embedButtonVisibilityConfig={{
    defaultDelayMs: 2000,   // applies to /dashboard and /settings
    groups: [
      {
        id: 'support',
        screens: ['/help'],   // /help gets its own group config
        continuity: 'perScreen',
        delayMs: 5000,
        delayPolicy: 'perScreen',
      },
    ],
  }}
>
  {children}
</EmbedProvider>
```

***

## Level 6 — Reading Current Path in Children [#level-6--reading-current-path-in-children]

Any component inside `EmbedProvider` can access the current path via `useEmbed`:

```tsx
import { useEmbed } from '@revrag-ai/embed-react';

function Breadcrumb() {
  const { currentPath } = useEmbed();

  return <nav>You are at: {currentPath}</nav>;
}
```

> `useEmbed()` throws if called outside `EmbedProvider`. Always use it inside the provider tree.

***

## Complete Real-World Example [#complete-real-world-example]

A Next.js app with multiple sections, each with their own widget behavior:

```tsx
// app/layout.tsx
"use client";

import { usePathname } from 'next/navigation';
import { useInitialize, EmbedProvider } from '@revrag-ai/embed-react';
import '@revrag-ai/embed-react/dist/ai-assistant-widget.css';
import type { EmbedButtonVisibilityConfig } from '@revrag-ai/embed-react';

function useNextPathname() {
  return usePathname();
}

const visibilityConfig: EmbedButtonVisibilityConfig = {
  defaultDelayMs: 1500,
  groups: [
    {
      // Onboarding: delay once per session, stay visible through all steps
      id: 'onboarding',
      screens: ['/welcome', '/setup', '/verify'],
      continuity: 'continuous',
      delayMs: 6000,
      delayPolicy: 'oncePerAppSession',
    },
    {
      // Checkout: delay once per entry, no re-animation between steps
      id: 'checkout',
      screens: ['/cart', '/checkout', '/payment', '/order-confirmed'],
      continuity: 'continuous',
      delayMs: 3000,
      delayPolicy: 'oncePerGroupEntry',
    },
    {
      // Support: always delay, re-animate on each page (high intent section)
      id: 'support',
      screens: ['/help', '/faq', '/contact'],
      continuity: 'perScreen',
      delayMs: 2000,
      delayPolicy: 'perScreen',
    },
  ],
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  const { error } = useInitialize("your-api-key");

  if (error) console.error('[EmbedSDK] Init error:', error);

  return (
    <html lang="en">
      <body>
        <EmbedProvider
          usePathHook={useNextPathname}
          matchMode="startsWith"
          embedButtonVisibilityConfig={visibilityConfig}
          embedButtonProps={{ positioning: 'fixed', bottomOffset: 0 }}
          embedButtonPosition={{ bottom: 24, right: 24 }}
        >
          {children}
        </EmbedProvider>
      </body>
    </html>
  );
}
```

***

## Props Quick Reference [#props-quick-reference]

| Prop                          | Type                          | Default                     | Purpose                                  |
| ----------------------------- | ----------------------------- | --------------------------- | ---------------------------------------- |
| `children`                    | `ReactNode`                   | —                           | Your app content                         |
| `currentPath`                 | `string`                      | —                           | Manual path override (highest priority)  |
| `usePathHook`                 | `() => string`                | —                           | Router hook for automatic path detection |
| `includeScreens`              | `string[]`                    | `[]` (all)                  | Routes where the widget appears          |
| `matchMode`                   | `"exact" \| "startsWith"`     | `"exact"`                   | How routes are matched                   |
| `embedButtonDelayMs`          | `number`                      | `0`                         | Global delay before widget appears (ms)  |
| `embedButtonVisibilityConfig` | `EmbedButtonVisibilityConfig` | —                           | Advanced group-based visibility          |
| `embedButtonProps`            | `EmbedButtonProps`            | —                           | Props forwarded to `<EmbedButton />`     |
| `embedButtonPosition`         | `{ bottom?, right? }`         | `{ bottom: 20, right: 16 }` | Fixed position of the floating button    |

***

## Common Mistakes [#common-mistakes]

**Passing the hook result instead of the hook itself:**

```tsx
// ❌ Wrong — passes the path string, not the hook
<EmbedProvider usePathHook={usePathname()} />

// ✅ Correct — passes the hook function
function useNextPathname() { return usePathname(); }
<EmbedProvider usePathHook={useNextPathname} />
```

**Using `useEmbed` outside the provider:**

```tsx
// ❌ Throws an error
function ComponentOutsideProvider() {
  const { currentPath } = useEmbed(); // Error!
}

// ✅ Must be inside EmbedProvider tree
function ComponentInsideProvider() {
  const { currentPath } = useEmbed(); // Works
}
```

**Expecting `includeScreens` + groups to be separate:**

Screens listed in `groups[].screens` are **automatically added** to the include list — you don't need to repeat them in `includeScreens`.

```tsx
// ✅ You don't need to list /help in includeScreens — it's already in the group
<EmbedProvider
  embedButtonVisibilityConfig={{
    groups: [{ id: 'support', screens: ['/help'], continuity: 'perScreen' }]
  }}
/>
```

**Forgetting `"use client"` in Next.js App Router:**

```tsx
// ✅ Required when using EmbedProvider in Next.js App Router
"use client";

import { EmbedProvider } from '@revrag-ai/embed-react';
```
