Custom Autocomplete Suggestion List

Autocomplete suggestion list components can be customized via Channel props:

Best Practices

  • Preserve default components for triggers you don't customize to keep expected behavior.
  • Keep list rendering lightweight; suggestion updates happen on every keystroke.
  • Use keyboardShouldPersistTaps="always" in custom lists to avoid dismissing the keyboard.
  • Normalize triggerType mapping (/, :, @) to avoid mismatched UI.
  • Ensure onSelect updates the composer state rather than mutating input directly.

Header and item components are used inside the list's FlatList. These props let you customize the header, items, or the entire list.

The available suggestion trigger types are:

  • command for /
  • emoji for :
  • mention for @

You can render custom UI for specific triggers and fall back to defaults for the rest.

Customizing Header component

The header renders at the top of the list and is customized via AutoCompleteSuggestionHeader.

The props for the component are:

  • triggerType: The trigger type of the suggestion list.
  • queryText: The value to display in the header.

An example for the same would be as follows:

import { AutoCompleteSuggestionHeader } from "stream-chat-react-native";
import { Text } from "react-native";

<Channel
  AutoCompleteSuggestionHeader={({ queryText, triggerType }) => {
    if (triggerType === "command") {
      return <Text>Command Header Component</Text>;
    } else if (triggerType === "emoji") {
      return <Text>Emoji Header Component</Text>;
    } else {
      return (
        <AutoCompleteSuggestionHeader
          queryText={queryText}
          triggerType={triggerType}
        />
      );
    }
  }}
>
  {/* The underlying components */}
</Channel>;

Note: Return the default AutoCompleteSuggestionHeader to keep default behavior for a trigger.

Customizing Item Component

Customize list items via the AutoCompleteSuggestionItem prop on Channel.

The props for the component are as follows:

  • triggerType: The trigger type of the suggestion list.
  • itemProps: Varies by trigger. command has name and args, emoji is an Emoji, and mention is SuggestionUser<Us>.
import { Avatar, AutoCompleteSuggestionItem } from "stream-chat-react-native";
import { Text, View } from "react-native";

<Channel
  AutoCompleteSuggestionItem={({ itemProps, triggerType }) => {
    if (triggerType === "command") {
      return (
        <View>
          <Text>{itemProps.name}</Text>
          <Text>{itemProps.args}</Text>
        </View>
      );
    } else if (triggerType === "mention") {
      const { id, image, name, online } = itemProps;
      return (
        <View>
          <Avatar image={image} name={name} online={online} size={30} />
          <Text>{itemProps.name}</Text>
        </View>
      );
    } else {
      return (
        <AutoCompleteSuggestionItem
          itemProps={itemProps}
          triggerType={triggerType}
        />
      );
    }
  }}
>
  {/*The underlying components*/}
</Channel>;

Note: Return the default AutoCompleteSuggestionItem to keep default behavior for a trigger.

Customizing the AutoComplete suggestion list

You can also replace the entire list via AutoCompleteSuggestionList on Channel.

The props available to the component are:

  • AutoCompleteSuggestionHeader - The header component to be used in the list.
  • AutoCompleteSuggestionItem - The item component to be used in the list.

Example:

import {
  AutoCompleteSuggestionHeader,
  AutoCompleteSuggestionItem,
  useMessageComposer,
  useStateStore,
  Channel,
} from "stream-chat-react-native";
import { FlatList, Text, View } from "react-native";
import { TextComposerState, SearchSourceState } from "stream-chat";

const textComposerStateSelector = (state: TextComposerState) => ({
  suggestions: state.suggestions,
  text: state.text,
});

const searchSourceStateSelector = (nextValue: SearchSourceState) => ({
  items: nextValue.items,
});

const CustomAutoCompleteSuggestionList = () => {
  const messageComposer = useMessageComposer();
  const { textComposer } = messageComposer;
  const { suggestions } = useStateStore(
    textComposer.state,
    textComposerStateSelector,
  );
  const { items } =
    useStateStore(suggestions?.searchSource.state, searchSourceStateSelector) ??
    {};
  const trigger = suggestions?.trigger;
  const queryText = suggestions?.query;

  const triggerType = {
    "/": "command",
    ":": "emoji",
    "@": "mention",
  }[trigger ?? ""];

  const onSelect = (item) => {
    // Handle the selection of the item
    console.log("Selected item:", item);
  };

  if (triggerType === "command") {
    return (
      <View>
        <View>
          <Text>Command suggestions for {queryText}</Text>
        </View>
        {data.map((item) => (
          <AutoCompleteSuggestionItem
            itemProps={item}
            key={item.name}
            triggerType={triggerType}
          />
        ))}
      </View>
    );
  } else if (triggerType === "emoji") {
    return (
      <FlatList
        data={data}
        keyboardShouldPersistTaps="always"
        ListHeaderComponent={
          <AutoCompleteSuggestionHeader
            queryText={queryText}
            triggerType={triggerType}
          />
        }
        renderItem={({ index, item }) => (
          <TouchableOpacity
            onPress={() => {
              onSelect(item);
            }}
          >
            <Text>{item.unicode}</Text>
          </TouchableOpacity>
        )}
      />
    );
  } else {
    return (
      <View>
        <AutoCompleteSuggestionHeader
          queryText={queryText}
          triggerType={triggerType}
        />
        {data.map((item) => (
          <AutoCompleteSuggestionItem
            itemProps={item}
            key={item.name}
            triggerType={triggerType}
          />
        ))}
      </View>
    );
  }
};

<Channel AutoCompleteSuggestionList={CustomAutoCompleteSuggestionList}>
  {/*The underlying components*/}
</Channel>;

Note: Return the default AutoCompleteSuggestionList to keep default behavior for a trigger.