Info:
This is release candidate documentation for Stream Chat React SDK v15. For the latest stable version, see the latest version (v14).

Hide history for new members

This recipe builds a dialog that asks how much of the conversation history a new member sees when they're added to a channel. The options behind it, and what the new member sees, are explained in History visibility for new members.

The dialog offers four choices:

Dialog for the past conversation history access restriction

Minimal dialog to control history visibility when adding members

On Add, the dialog calls Channel.addMembers() with the option that matches the choice:

  • don't include: no history → hide_history: true
  • from today: only today → hide_history_before set to the start of today
  • from the beginning: everything → no option
  • custom: from a date and time the user picks → hide_history_before set to that Date

hide_history_before is a Date, and it must be in the past.

The dialog is a GlobalModal from the SDK with Prompt inside it. The modal handles the overlay, the focus and closing on Escape, and Prompt lays out the title, the body and the buttons. Dialogs explains both.

src/AddMembersButton.tsx
import type { ChannelUpdateOptions } from "stream-chat";
import { useState } from "react";
import { GlobalModal, Prompt, useChannel } from "stream-chat-react";
import "./AddMembersButton.css";

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

const historyOptions: { label: string; mode: HistoryMode }[] = [
  { label: "don't include", mode: "none" },
  { label: "from today", mode: "today" },
  { label: "from the beginning", mode: "all" },
  { label: "custom", mode: "custom" },
];

// The addMembers options for a choice, or undefined when the custom date isn't valid.
const getHistoryOptions = (
  mode: HistoryMode,
  customInput: string,
): ChannelUpdateOptions | undefined => {
  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 };
  }

  const cutoff = new Date(customInput);
  if (Number.isNaN(cutoff.getTime()) || cutoff > new Date()) return undefined;
  return { hide_history_before: cutoff };
};

export const AddMembersButton = ({ memberIds }: { memberIds: string[] }) => {
  const channel = useChannel();
  const [open, setOpen] = useState(false);
  const [mode, setMode] = useState<HistoryMode>("none");
  const [customInput, setCustomInput] = useState("");

  const options = getHistoryOptions(mode, customInput);
  const close = () => setOpen(false);

  const handleAdd = async () => {
    if (!options) return;
    try {
      await channel.addMembers({ members: memberIds, ...options });
      close();
    } catch (error) {
      console.error("Could not add the members", error);
    }
  };

  return (
    <>
      <button onClick={() => setOpen(true)} type="button">
        Add members
      </button>
      <GlobalModal onClose={close} open={open}>
        <Prompt.Root>
          <Prompt.Header close={close} title="Include conversation history?" />
          <Prompt.Body>
            <div className="history-options">
              {historyOptions.map((option) => (
                <label className="history-option" key={option.mode}>
                  <input
                    checked={mode === option.mode}
                    name="history"
                    onChange={() => setMode(option.mode)}
                    type="radio"
                    value={option.mode}
                  />
                  <span>{option.label}</span>
                </label>
              ))}
            </div>
            {mode === "custom" && (
              <div className="history-custom">
                <input
                  aria-label="Show history from"
                  className="history-custom__input"
                  onChange={(event) => setCustomInput(event.target.value)}
                  type="datetime-local"
                  value={customInput}
                />
                {!options && (
                  <div className="history-custom__error">
                    Enter a valid date and time in the past
                  </div>
                )}
              </div>
            )}
          </Prompt.Body>
          <Prompt.Footer>
            <Prompt.FooterControls>
              <Prompt.FooterControlsButtonSecondary onClick={close}>
                Cancel
              </Prompt.FooterControlsButtonSecondary>
              <Prompt.FooterControlsButtonPrimary
                disabled={!options}
                onClick={handleAdd}
              >
                Add
              </Prompt.FooterControlsButtonPrimary>
            </Prompt.FooterControls>
          </Prompt.Footer>
        </Prompt.Root>
      </GlobalModal>
    </>
  );
};
src/AddMembersButton.css
.history-options {
  display: grid;
  gap: 8px;
}

.history-option {
  align-items: center;
  display: flex;
  gap: 8px;
}

.history-custom {
  margin-top: 12px;
}

.history-custom__input {
  box-sizing: border-box;
  padding: 8px 10px;
  width: 100%;
}

.history-custom__error {
  color: #b91c1c;
  font-size: 12px;
  margin-top: 6px;
}

Render AddMembersButton inside a channel, for example in a header of your own, with the ids of the users to add. useChannel() gives it the channel. Adding members needs the update-channel-members capability.

Best practices

  • Default to the most private choice, "don't include", unless your app has a reason to share history.
  • Pass a Date, not a string, and check that it's in the past before calling addMembers.
  • Say in the dialog what the new member will see.
  • Handle a failed request. addMembers rejects when the user may not add members or the date isn't valid. The dialog above logs the error and stays open; show the user a message in your app.
  • Leave the rule to the server when the user shouldn't choose: one function that always passes the same options, as on History visibility for new members.

Mapping to the raw API

The dialog's choices are the options of a single call. For example, this adds Thierry with the last seven days of history:

const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - 7);

await channel.addMembers({
  members: ["thierry"],
  hide_history_before: cutoff,
});

The second argument is an optional message that announces the change in the channel. undefined adds the member without one.