---
id: link-previews
sidebar_position: 21
title: Link Previews in Message Input
---

import LinkPreviewMessageInput from '../../assets/link-preview-message-input.png';
import LinkPreviewEditMessageForm from '../../assets/link-preview-edit-message-form.png';

The purpose of link previews in the `MessageInput` is to provide visual guides of what a user may expect to be rendered later in the `MessageList` by [`Card` component](../../../components/message-components/attachment#card) among message attachments.

## Rendering of link previews

The link previews are rendered using `LinkPreviewList`. The component accepts a single prop `linkPreviews` which is an array of `LinkPreview` objects.

### The default LinkPreviewList component

The default `LinkPreviewList` component lists all the successfully loaded previews.

The default link preview UI is implemented for:

**Message input**

<img src={LinkPreviewMessageInput} />

<hr />

**Edit message form**

<img src={LinkPreviewEditMessageForm} />

### Enabling link previews

Link previews have to be enabled in two places:

- [**channel config property `url-enrichment`**](https://getstream.io/chat/docs/javascript/channel-level_settings/?language=javascript&q=url_enrichment#list-of-settings-that-can-be-overridden) - enabled by default
- `enrichURLForPreview` prop - disabled by default

Those who have not previously disabled `url-enrichment` in the channel config, can enable link previews in `MessageInput` by setting `enrichURLForPreview` in one of the following places:

**Channel props**

```tsx
import {
  Channel,
  ChannelHeader,
  VirtualizedMessageList as MessageList,
  MessageInput,
  Thread,
  Window,
} from 'stream-chat-react';

const App = () => (
  // highlight-next-line
  <Channel enrichURLForPreview>
    <Window>
      <ChannelHeader />
      <MessageList />
      <MessageInput />
    </Window>
    <Thread />
  </Channel>
);

export default App;
```

**MessageList or VirtualizedMessageList (applied to EditMessageForm)**

```tsx
import { Channel, VirtualizedMessageList as MessageList } from 'stream-chat-react';

const App = () => (
  <Channel>
    {/* ... */}
    // highlight-start
    <MessageList
      additionalMessageInputProps={{
        urlEnrichmentConfig: { enrichURLForPreview: true },
      }}
    />
    // highlight-end
    {/* ... */}
  </Channel>
);

export default App;
```

**Thread (applied to MessageInput)**

```tsx
import { Channel, Thread } from 'stream-chat-react';

const App = () => (
  <Channel>
    {/* ... */}
    // highlight-start
    <Thread
      additionalMessageInputProps={{
        urlEnrichmentConfig: { enrichURLForPreview: true },
      }}
    />
    // highlight-end
  </Channel>
);

export default App;
```

**MessageInput**

```tsx
import { Channel, MessageInput } from 'stream-chat-react';

const App = () => (
  <Channel>
    {/* ... */}
    // highlight-next-line
    <MessageInput urlEnrichmentConfig={{ enrichURLForPreview: true }} />
    {/* ... */}
  </Channel>
);

export default App;
```

## Link Preview customization

### Custom rendering of link previews

If the default link previews UI does not meet our expectations, we can provide a custom component. To render our own `LinkPreviewList`, we just need to pass it to `Channel` prop `LinkPreviewList`. The component will be passed `linkPreviews`, an array of `LinkPreview` objects.

```tsx
import { Channel, LinkPreviewListProps, LinkPreviewState } from 'stream-chat-react';

import { LinkPreviewCardLoaded, LinkPreviewCardLoading } from './LinkPreviewCard';

const CustomLinkPreviewList = ({ linkPreviews }: LinkPreviewListProps) => {
  const showLinkPreviews = linkPreviews.length > 0;

  if (!showLinkPreviews) return null;

  return (
    <div className='str-chat__link-preview-list'>
      {Array.from(linkPreviews.values()).map((linkPreview) => {
        switch (linkPreview.state) {
          case LinkPreviewState.LOADED:
            return (
              <LinkPreviewCardLoaded key={linkPreview.og_scrape_url} linkPreview={linkPreview} />
            );
          case LinkPreviewState.LOADING:
            return (
              <LinkPreviewCardLoading key={linkPreview.og_scrape_url} linkPreview={linkPreview} />
            );
          case LinkPreviewState.QUEUED:
            return (
              <LinkPreviewCardLoading key={linkPreview.og_scrape_url} linkPreview={linkPreview} />
            );
          default:
            return null;
        }
      })}
    </div>
  );
};

const App = () => (
  // highlight-next-line
  <Channel LinkPreviewList={CustomLinkPreviewList} enrichURLForPreview>
    {/* ...  */}
  </Channel>
);

export default App;
```

### Link preview states

In the above example we can notice, that the `LinkPreview` object comes with property `state`. This property can be used to determine, how the preview for a given link should be rendered. These are the possible states a link preview can acquire:

```typescript
enum LinkPreviewState {
  /** Link preview has been dismissed using MessageInputContextValue.dismissLinkPreview **/
  DISMISSED = 'dismissed',
  /** Link preview could not be loaded, the enrichment request has failed. **/
  FAILED = 'failed',
  /** Link preview has been successfully loaded. **/
  LOADED = 'loaded',
  /** The enrichment query is in progress for a given link. **/
  LOADING = 'loading',
  /** The link is scheduled for enrichment. **/
  QUEUED = 'queued',
}
```

### Behavior customization

The following aspect of link preview management in `MessageInput` can be customized:

- The debounce interval for the URL discovery and enrichment requests.
- URL discovery
- Link preview dismissal

In general, the behavior can be customized in two ways:

1. globally through `Channel` props (`enrichURLForPreviewConfig`)
2. with more granularity over `MessageList`, `VirtualizedMessageList`, `Thread`, `MessageInput` props (`additionalMessageInputProps.urlEnrichmentConfig`)

#### Custom debounce interval

The default debounce interval is 1.5 seconds. The URL discovery and enrichment will thus not start until the stops typing for at least 1.5 seconds. This interval can be increased or decreased by passing `debounceURLEnrichmentMs` configuration parameter.

Global debounce interval configuration over `Channel`:

```tsx
import {
  Channel,
  MessageInput,
  Thread,
  VirtualizedMessageList as MessageList,
} from 'stream-chat-react';

const debounceURLEnrichmentMs = 1000;

const App = () => (
  <Channel enrichURLForPreview enrichURLForPreviewConfig={{ debounceURLEnrichmentMs }}>
    <MessageList />
    <MessageInput />
    <Thread />
  </Channel>
);
```

Local debounce configuration:

```tsx
import {
  Channel,
  MessageInput,
  Thread,
  VirtualizedMessageList as MessageList,
} from 'stream-chat-react';

const debounceURLEnrichmentMs = 1000;
const additionalMessageInputProps = {
  urlEnrichmentConfig: { debounceURLEnrichmentMs },
};

const App = () => (
  <Channel enrichURLForPreview>
    <MessageList additionalMessageInputProps={additionalMessageInputProps} />
    <MessageInput urlEnrichmentConfig={{ debounceURLEnrichmentMs }} />
    <Thread additionalMessageInputProps={additionalMessageInputProps} />
  </Channel>
);
```

#### Custom text parsing function

If the default link discovery functionality is not sufficient, this can be overridden by providing `findURLFn` custom function. The requirement is that the function returns an array of strings - links - that will be later used to scrape the data.

The parameter set globally over `Channel` props:

```tsx
import {
  Channel,
  MessageInput,
  Thread,
  VirtualizedMessageList as MessageList,
} from 'stream-chat-react';

import { searchForURLs } from '../utils';

const App = () => (
  <Channel enrichURLForPreview enrichURLForPreviewConfig={{ findURLFn: searchForURLs }}>
    <MessageList />
    <MessageInput />
    <Thread />
  </Channel>
);
```

Local configuration of URL discovery function:

```tsx
import {
  Channel,
  MessageInput,
  Thread,
  VirtualizedMessageList as MessageList,
} from 'stream-chat-react';

import { searchForURLs } from '../utils';

const additionalMessageInputProps = {
  urlEnrichmentConfig: { findURLFn: searchForURLs },
};

const App = () => (
  <Channel enrichURLForPreview>
    <MessageList additionalMessageInputProps={additionalMessageInputProps} />
    <MessageInput urlEnrichmentConfig={{ findURLFn: searchForURLs }} />
    <Thread additionalMessageInputProps={additionalMessageInputProps} />
  </Channel>
);
```

#### Custom actions on link preview dismissal

When a link preview is dismissed, it's state is set to `'dismissed'`. This behavior can be expanded (not changed) by providing `onLinkPreviewDismissed` callback. The callback is invoked at the beginning of the dismissal procedure. It is then followed by state update marking the given URL preview as `'dismissed'`.

The `onLinkPreviewDismissed` callback can be passed to `Channel` prop `enrichURLForPreviewConfig`:

```tsx
import {
  Channel,
  LinkPreview,
  MessageInput,
  Thread,
  VirtualizedMessageList as MessageList,
} from 'stream-chat-react';

const onLinkPreviewDismissed = (linkPreview: LinkPreview) => {
  // custom logic to invoke, when a given link preview is dismissed
};

const App = () => (
  <Channel enrichURLForPreview enrichURLForPreviewConfig={{ onLinkPreviewDismissed }}>
    <MessageList />
    <MessageInput />
    <Thread />
  </Channel>
);
```

The configuration can be passed individually to `MessageList`, `VirtualizedMessageList`, `Thread`, `MessageInput`:

```tsx
import {
  Channel,
  LinkPreview,
  MessageInput,
  Thread,
  VirtualizedMessageList as MessageList,
} from 'stream-chat-react';

const onLinkPreviewDismissed = (linkPreview: LinkPreview) => {
  // custom logic to invoke, when a given link preview is dismissed
};

const additionalMessageInputProps = {
  urlEnrichmentConfig: { onLinkPreviewDismissed },
};

const App = () => (
  <Channel enrichURLForPreview>
    <MessageList additionalMessageInputProps={additionalMessageInputProps} />
    <MessageInput urlEnrichmentConfig={{ onLinkPreviewDismissed }} />
    <Thread additionalMessageInputProps={additionalMessageInputProps} />
  </Channel>
);
```

### EnrichURLsController API

In case we would aspire at implementing custom `MessageInput` components that would require control over link previews, we can access the API over the `MessageInputContext` value. This is the API that allows us:

- to trigger URL search in message text and enrichment - `findAndEnqueueURLsToEnrich`
- cancel the URL enrichment (for example when submitting a message) - `cancelURLEnrichment`
- dismiss the loaded link previews assigning them with state `dismissed` - `dismissLinkPreview`

```tsx
import { useMessageInputContext } from 'stream-chat-react';

const CustomMessageInputUI = () => {
  const {
    cancelURLEnrichment,
    findAndEnqueueURLsToEnrich,
    dismissLinkPreview,
  } = useMessageInputContext();

  // ...
};

const App = () => {
  <Channel Input={CustomMessageInputUI} enrichURLForPreview>
    {/* ... */}
  </Channel>;
};
```

:::note
The `findAndEnqueueURLsToEnrich` function serves as an indicator, whether the link preview feature is actually enabled in the application.
:::
