Link Previews in Message Input

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 among message attachments.

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

Link Preview Message Input


Edit message form

Link Preview Edit Message Form

Link previews have to be enabled in two places:

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

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

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

export default App;

MessageList or VirtualizedMessageList (applied to EditMessageForm)

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

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

export default App;

Thread (applied to MessageInput)

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

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

export default App;

MessageInput

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

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

export default App;

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.

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 = () => (
  <Channel LinkPreviewList={CustomLinkPreviewList} enrichURLForPreview>
    {/* ...  */}
  </Channel>
);

export default App;

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:

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:

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:

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:

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:

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>
);

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:

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:

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
import { useMessageInputContext } from 'stream-chat-react';

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

  // ...
};

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

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

© Getstream.io, Inc. All Rights Reserved.