This is beta documentation for Stream Chat React Native SDK v9. For the latest stable version, see the latest version (v8) .

Hide Channel History For Newly Added Members

This tutorial shows how to build a simple modal that controls how much channel history a newly added member can see.

Best Practices

  • Make history visibility an explicit choice in the add-member flow.
  • Use server-side hide_history_before for consistency across clients.
  • Default to the safest option if no selection is made.
  • Validate custom dates before sending to prevent invalid updates.
  • Communicate the choice clearly to admins and users being added.

Example modal:

Modal for the past conversation history access restriction

Minimal modal to control history visibility when adding members

The UI below lets you choose how much of the conversation history a newly added member will see. On Add, it calls channel.addMembers() with hide_history_before mapped from the selected option:

  • don't include: hide all history before now → hide_history_before = new Date().toISOString()
  • from today: show only today → hide_history_before = startOfToday.toISOString()
  • from the beginning: show everything → omit hide_history_before
  • custom: show from a specific date/time → hide_history_before = new Date(customDate).toISOString()

Minimal styles are used for the modal, with v9 semantic tokens where available.

import { useState } from "react";
import { Channel, UserResponse } from "stream-chat";
import { Modal, Pressable, StyleSheet, Text, View } from "react-native";
import { useTheme } from "stream-chat-react-native";
import DateTimePicker, {
  DateTimePickerEvent,
} from "@react-native-community/datetimepicker";

export type AddMemberWithHistoryModalProps = {
  channel?: Channel;
  visible: boolean;
  onClose: () => void;
  user: UserResponse | null;
  onMemberAdded: () => void;
};

type SelectedOption = "none" | "today" | "all" | "custom";

export const AddMemberWithHistoryModal = (
  props: AddMemberWithHistoryModalProps,
) => {
  const [selectedOption, setSelectedOption] = useState<SelectedOption>("none");
  const [customDate, setCustomDate] = useState<Date>(new Date());
  const { semantics } = useTheme();
  const { channel, visible, onClose, user, onMemberAdded } = props;
  const accentColor = semantics.accentPrimary;

  const options: { name: string; value: SelectedOption }[] = [
    { name: "Don't Include History", value: "none" },
    {
      name: "From Today",
      value: "today",
    },
    { name: "From the beginning", value: "all" },
    { name: "Custom", value: "custom" },
  ];

  const handleAdd = async () => {
    if (!channel || !user) {
      return;
    }

    const updateOptions: { hide_history_before?: string } = {};

    if (selectedOption !== "all") {
      let cutoffDate = new Date();
      if (selectedOption === "today") {
        const startOfToday = new Date();
        startOfToday.setHours(0, 0, 0, 0);
        cutoffDate = startOfToday;
      } else if (selectedOption === "custom") {
        if (Number.isNaN(customDate.getTime())) {
          return;
        }
        cutoffDate = customDate;
      }
      updateOptions.hide_history_before = cutoffDate.toISOString();
    }

    try {
      await channel.addMembers([user.id], undefined, updateOptions);
      onMemberAdded();
    } catch (error) {
      console.error("Error adding member with history: ", error);
    }
  };

  const setDate = (event: DateTimePickerEvent, date?: Date) => {
    const { type } = event;
    if (type === "set") {
      if (date) {
        setCustomDate(date);
      }
    }
  };

  return (
    <Modal
      animationType="slide"
      transparent={true}
      visible={visible}
      onRequestClose={onClose}
    >
      <View style={styles.container}>
        <View
          style={[
            styles.modal,
            {
              backgroundColor: semantics.backgroundElevationElevation1,
              borderColor: semantics.borderCoreDefault,
              shadowColor: semantics.textPrimary,
            },
          ]}
        >
          <Text style={[styles.title, { color: semantics.textPrimary }]}>
            Include Conversation History?
          </Text>
          {options.map((option) => (
            <View style={styles.option} key={option.value}>
              <Pressable
                style={[styles.radioButton, { borderColor: accentColor }]}
                onPress={() => setSelectedOption(option.value)}
              >
                {selectedOption === option.value && (
                  <View
                    style={[
                      styles.radioButtonInner,
                      { backgroundColor: accentColor },
                    ]}
                  />
                )}
              </Pressable>
              <Text
                style={[styles.optionText, { color: semantics.textPrimary }]}
              >
                {option.name}
              </Text>
            </View>
          ))}
          {selectedOption === "custom" && (
            <View style={styles.datePickerContainer}>
              <DateTimePicker
                mode="datetime"
                onChange={setDate}
                value={customDate}
                display="default"
              />
            </View>
          )}
          <View style={styles.buttonContainer}>
            <Pressable style={styles.button} onPress={onClose}>
              <Text style={[styles.buttonText, { color: accentColor }]}>
                Cancel
              </Text>
            </Pressable>
            <Pressable style={styles.button} onPress={handleAdd}>
              <Text style={[styles.buttonText, { color: accentColor }]}>
                Add
              </Text>
            </Pressable>
          </View>
        </View>
      </View>
    </Modal>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
  },
  modal: {
    margin: 20,
    borderRadius: 16,
    borderWidth: 1,
    padding: 20,
    shadowOffset: {
      width: 0,
      height: 2,
    },
    shadowOpacity: 0.25,
    shadowRadius: 4,
    elevation: 5,
  },
  title: {
    fontSize: 18,
    fontWeight: "bold",
    textAlign: "center",
    marginBottom: 16,
  },
  option: {
    flexDirection: "row",
    alignItems: "center",
    paddingVertical: 8,
  },
  optionText: {
    fontSize: 16,
    marginLeft: 8,
  },
  radioButton: {
    width: 20,
    height: 20,
    borderRadius: 10,
    borderWidth: 2,
    alignItems: "center",
    justifyContent: "center",
  },
  radioButtonInner: {
    width: 12,
    height: 12,
    borderRadius: 6,
  },
  buttonContainer: {
    flexDirection: "row",
    justifyContent: "flex-end",
    marginTop: 8,
  },
  button: {
    padding: 8,
    marginLeft: 8,
  },
  buttonText: {
    fontSize: 16,
    fontWeight: "bold",
    textAlign: "center",
  },
  datePickerContainer: {
    alignItems: "center",
    justifyContent: "center",
  },
});

Mapping to the raw API

Here’s a standalone snippet showing the parameter shape. This mirrors the button behavior above:

// Example: include only the last 7 days of history for the new member
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - 7);
await channel.addMembers(["thierry"], undefined, {
  hide_history_before: cutoff.toISOString(),
});