> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kontext.so/llms.txt
> Use this file to discover all available pages before exploring further.

# Guides

> Patterns and recipes for common React SDK integration tasks.

## Handling no-fill

Subscribe to `ad.no-fill` to know when no ad was returned (geo-restriction, frequency cap, server-side skip). The `skipCode` payload tells you why.

```tsx theme={null}
<AdsProvider
  // ...
  onEvent={({ name, payload }) => {
    if (name === 'ad.no-fill') {
      console.log('No ad available:', payload.skipCode)
    }
  }}
>
```

Every preload produces exactly one of `ad.filled` / `ad.no-fill` / `ad.error` per response, so you can rely on hearing back on every user message.

## Pass every message, suppress ads with `trackOnly`

Always feed every message into the session, even when you don't want an ad to appear (e.g. user is on a free trial, in a no-ads region, or you've decided to show ads only every Nth message). Skipping `addMessage` calls breaks the conversation context the server relies on for targeting. See [Pacing](/concepts/pacing) for the full pattern.

Use `{ trackOnly: true }` to send the preload for analytics without generating an ad:

```tsx theme={null}
const { addMessage } = useAds()
const shouldShowAd = userMessageCount % 5 === 0

addMessage(
  { id: msg.id, role: 'user', content: msg.content, createdAt: new Date() },
  { trackOnly: !shouldShowAd }
)
```

When `trackOnly: true`, the preload still fires (server keeps full analytics) but no `ad.filled` event will arrive for that message and no `<InlineAd>` will render content.

## Live-updating consent mid-session

`<AdsProvider>` reactively passes new props into the underlying session. Bind `regulatory` to your CMP state and the next preload picks up the new value automatically:

```tsx theme={null}
<AdsProvider
  publisherToken={publisherToken}
  userId={userId}
  conversationId={conversationId}
  regulatory={{ gdpr: gdprConsent.applies ? 1 : 0, gdprConsent: gdprConsent.tcString }}
>
  <Chat />
</AdsProvider>
```

No session recreation needed.

## Switching character

The active `character` cannot be live-updated — the accumulated message history belongs to the original persona, so swapping mid-session would leave messages targeted at the wrong character.

To switch character, change the `conversationId` (and `character`) prop on `<AdsProvider>`. The provider tears down the previous session and spins up a new one automatically:

```tsx theme={null}
<AdsProvider
  publisherToken={publisherToken}
  userId={userId}
  conversationId={conversationId}
  character={currentCharacter}
>
  <Chat />
</AdsProvider>
```

The same applies to `publisherToken`, `userId`, `conversationId`, and `enabledPlacementCodes` — changing any of those rebuilds the session.

## Loading older messages (conversation restore)

When restoring a conversation from your backend, call `addMessage(...)` once per historical message in order. Preloads are debounced (\~10 ms), so rapid sequential calls coalesce into a single preload for the most recent user message — you won't fire one preload per restored message.

```tsx theme={null}
const { addMessage } = useAds()

useEffect(() => {
  for (const m of loadedFromBackend) {
    addMessage({
      id: m.id,
      role: m.role,
      content: m.content,
      createdAt: m.timestamp,
    })
  }
}, [loadedFromBackend])
```
