# Hide history for new members

> **Info:** This is release candidate documentation for **Stream Chat React Native SDK v10**. For the latest stable version, see the [latest version (v9)](https://getstream.io/chat/docs/sdk/react-native/).

This UI cookbook builds a modal that asks how much of the conversation history new members see when they're added to a channel. The options behind it, and what the new members see, are explained in [History visibility for new members](https://getstream.io/chat/docs/sdk/react-native/v10/features/channels/history-visibility-for-new-members/).

## Best practices

- Default to the most private choice, "Don't include history", unless your app has a reason to share it.
- Pass `hide_history_before` a `Date`, not a string, and check that it's in the past before calling `Channel.addMembers()`.
- Say in the modal what the new members will see.
- Handle a failed request. `Channel.addMembers()` rejects when the user may not add members or the date isn't valid. Keep the modal open and say what went wrong.
- Leave the rule to your code when the user shouldn't choose: one function that always passes the same options, as in [Hiding history for every member your app adds](https://getstream.io/chat/docs/sdk/react-native/v10/features/channels/history-visibility-for-new-members/#hiding-history-for-every-member-your-app-adds).

## A modal to control history visibility when adding members

The modal offers four choices. On Add, it calls `Channel.addMembers()` with the options that match the choice:

- **Don't include history:** nothing sent before they join, `hide_history: true`.
- **From today:** `hide_history_before` set to the start of today.
- **From the beginning:** every message, no option.
- **Last days:** `hide_history_before` set to that many days ago. The user picks the number of days.

```tsx title="src/AddMembersModal.tsx"
import { useState } from "react";
import { Modal, Pressable, StyleSheet, Text, View } from "react-native";
import type { Channel, ChannelUpdateOptions } from "stream-chat";
import { useTheme } from "stream-chat-react-native";

type HistoryMode = "none" | "today" | "all" | "days";

const historyOptions: { label: string; mode: HistoryMode }[] = [
  { label: "Don't include history", mode: "none" },
  { label: "From today", mode: "today" },
  { label: "From the beginning", mode: "all" },
  { label: "Last days", mode: "days" },
];

const DAY_MS = 24 * 60 * 60 * 1000;

// The addMembers options for a choice.
const getHistoryOptions = (
  mode: HistoryMode,
  days: number,
): ChannelUpdateOptions => {
  if (mode === "all") return {};
  if (mode === "none") return { hide_history: true };

  if (mode === "today") {
    const startOfToday = new Date();
    startOfToday.setHours(0, 0, 0, 0);
    return { hide_history_before: startOfToday };
  }

  return { hide_history_before: new Date(Date.now() - days * DAY_MS) };
};

export type AddMembersModalProps = {
  channel: Channel;
  onAdded: () => void;
  onClose: () => void;
  userIds: string[];
  visible: boolean;
};

export const AddMembersModal = ({
  channel,
  onAdded,
  onClose,
  userIds,
  visible,
}: AddMembersModalProps) => {
  const {
    theme: { semantics },
  } = useTheme();
  const [mode, setMode] = useState<HistoryMode>("none");
  const [days, setDays] = useState(7);
  const [adding, setAdding] = useState(false);
  const [error, setError] = useState<string>();

  const add = async () => {
    setAdding(true);
    setError(undefined);
    try {
      await channel.addMembers({
        members: userIds,
        ...getHistoryOptions(mode, days),
      });
      onAdded();
    } catch {
      setError("The members couldn't be added. Try again.");
    } finally {
      setAdding(false);
    }
  };

  const textColor = { color: semantics.textPrimary };
  const accentColor = { color: semantics.accentPrimary };

  return (
    <Modal
      animationType="fade"
      onRequestClose={onClose}
      transparent
      visible={visible}
    >
      <View
        style={[
          styles.backdrop,
          { backgroundColor: semantics.backgroundCoreScrim },
        ]}
      >
        <View
          style={[
            styles.card,
            { backgroundColor: semantics.backgroundCoreElevation1 },
          ]}
        >
          <Text style={[styles.title, textColor]}>
            Include conversation history?
          </Text>
          {historyOptions.map((option) => {
            const selected = mode === option.mode;
            return (
              <Pressable
                accessibilityRole="radio"
                accessibilityState={{ checked: selected }}
                key={option.mode}
                onPress={() => setMode(option.mode)}
                style={styles.option}
              >
                <View
                  style={[
                    styles.radio,
                    { borderColor: semantics.accentPrimary },
                  ]}
                >
                  {selected ? (
                    <View
                      style={[
                        styles.radioDot,
                        { backgroundColor: semantics.accentPrimary },
                      ]}
                    />
                  ) : null}
                </View>
                <Text style={textColor}>{option.label}</Text>
              </Pressable>
            );
          })}
          {mode === "days" ? (
            <View style={styles.stepper}>
              <Pressable
                accessibilityLabel="Fewer days"
                accessibilityRole="button"
                disabled={days <= 1}
                onPress={() => setDays((value) => value - 1)}
                style={styles.stepperButton}
              >
                <Text style={[styles.stepperText, accentColor]}>-</Text>
              </Pressable>
              <Text style={textColor}>
                The last {days} {days === 1 ? "day" : "days"}
              </Text>
              <Pressable
                accessibilityLabel="More days"
                accessibilityRole="button"
                onPress={() => setDays((value) => value + 1)}
                style={styles.stepperButton}
              >
                <Text style={[styles.stepperText, accentColor]}>+</Text>
              </Pressable>
            </View>
          ) : null}
          {error ? (
            <Text style={[styles.error, { color: semantics.accentError }]}>
              {error}
            </Text>
          ) : null}
          <View style={styles.buttons}>
            <Pressable
              accessibilityRole="button"
              onPress={onClose}
              style={styles.button}
            >
              <Text style={[styles.buttonText, accentColor]}>Cancel</Text>
            </Pressable>
            <Pressable
              accessibilityRole="button"
              disabled={adding || userIds.length === 0}
              onPress={() => void add()}
              style={styles.button}
            >
              <Text style={[styles.buttonText, accentColor]}>
                {adding ? "Adding..." : "Add"}
              </Text>
            </Pressable>
          </View>
        </View>
      </View>
    </Modal>
  );
};

const styles = StyleSheet.create({
  backdrop: {
    alignItems: "center",
    flex: 1,
    justifyContent: "center",
    padding: 24,
  },
  button: { padding: 8 },
  buttonText: { fontSize: 16, fontWeight: "600" },
  buttons: {
    flexDirection: "row",
    gap: 8,
    justifyContent: "flex-end",
    marginTop: 8,
  },
  card: { alignSelf: "stretch", borderRadius: 16, padding: 20 },
  error: { marginTop: 8 },
  option: {
    alignItems: "center",
    flexDirection: "row",
    gap: 8,
    paddingVertical: 8,
  },
  radio: {
    alignItems: "center",
    borderRadius: 10,
    borderWidth: 2,
    height: 20,
    justifyContent: "center",
    width: 20,
  },
  radioDot: { borderRadius: 6, height: 12, width: 12 },
  stepper: {
    alignItems: "center",
    flexDirection: "row",
    gap: 12,
    paddingLeft: 28,
  },
  stepperButton: { paddingHorizontal: 12, paddingVertical: 4 },
  stepperText: { fontSize: 20, fontWeight: "600" },
  title: {
    fontSize: 18,
    fontWeight: "600",
    marginBottom: 12,
    textAlign: "center",
  },
});
```

<table>
  <tr>
  <td align='center' width='50%'>

![A modal asking Include conversation history with four choices on iOS](https://getstream.io/docs-assets/images/aecc00c4961d.png)

  </td>
  <td align='center' width='50%'>

![A modal asking Include conversation history with four choices on Android](https://getstream.io/docs-assets/images/989f0df57010.png)

  </td>
  </tr>
</table>

What happens when the user taps Add:

1. `getHistoryOptions` turns the choice into the options. "From today" and "Last days" compute the cutoff when Add is tapped, so it's always in the past.
2. `Channel.addMembers({ members, ...options })` sends one request. The server applies the cutoff to the new members only; the existing members' view doesn't change.
3. When it succeeds, the modal calls `onAdded`. The server's `member.added` events add the members to `channel.state.members`, so member lists and counts update.
4. When it fails, the modal stays open with its message, and the user can try again or cancel.

The colors come from the theme's semantic tokens, so the modal follows the app's theme and dark mode. `useTheme()` needs `Chat` above the modal; see [Theming](https://getstream.io/chat/docs/sdk/react-native/v10/ui/customizing/theming/#reading-the-theme-in-your-own-components).

The "Last days" choice uses a stepper so the modal needs no extra package. To let the user pick a calendar date instead, render a date picker, such as [`@react-native-community/datetimepicker`](https://github.com/react-native-datetimepicker/datetimepicker), and pass the start of the picked day as `hide_history_before`.

## Opening the modal

Open the modal once the user has picked whom to add, for example from a user picker of your own. Adding members needs the `update-channel-members` capability, so show the entry point only to users who have it:

```tsx title="src/AddMembersButton.tsx"
import { useState } from "react";
import { Button } from "react-native";
import {
  useChannel,
  useOwnCapabilitiesContext,
} from "stream-chat-react-native";
import { AddMembersModal } from "./AddMembersModal";

export const AddMembersButton = ({ userIds }: { userIds: string[] }) => {
  const channel = useChannel();
  const { updateChannelMembers } = useOwnCapabilitiesContext();
  const [visible, setVisible] = useState(false);

  if (!updateChannelMembers) return null;

  return (
    <>
      <Button
        disabled={userIds.length === 0}
        onPress={() => setVisible(true)}
        title="Add members"
      />
      <AddMembersModal
        channel={channel}
        onAdded={() => setVisible(false)}
        onClose={() => setVisible(false)}
        userIds={userIds}
        visible={visible}
      />
    </>
  );
};
```

`useChannel()` and `useOwnCapabilitiesContext()` read the channel and the user's capabilities from the `Channel` or `Thread` around the button, so render it inside one, for example in your channel header. Outside them, pass the channel as a prop, and read `ownCapabilities` from `channel.state` with `useStateStore` to check for `update-channel-members`.

The SDK's own Add form in channel details adds members without these options. To ask there instead, replace the form's header, as [Hiding history in the Add form](https://getstream.io/chat/docs/sdk/react-native/v10/features/channels/history-visibility-for-new-members/#hiding-history-in-the-add-form) shows, and open this modal from it.

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react-native/v10/ui-cookbook/app/hide-history-for-new-members/](https://getstream.io/chat/docs/sdk/react-native/v10/ui-cookbook/app/hide-history-for-new-members/).