There are a few obvious reasons that this might happen:


  1. You're using react classical components, and not unsubscribing in componentWillUnmount().
  2. You're using react functional components and you haven't returned a cleanup function that unsubscribes during a call to useEffect
  3. You're using a component that subscribes to a channel, which you use for multiple things, and your Ably Client is defined inside that component


The third issue is more subtle, and we shall illustrate it below.

Imagine you have a react component that looks like this:

export default class AblyMessageComponent extends React.Component {
  constructor() {
    super();
    this.state = { messages: [] };
    this.client = new Ably.Realtime("ably-api-key-here");
  }

  async componentDidMount() {
    this.channel = await this.client.channels.get("my-cool-channel");

    await this.channel.subscribe(message => {
      console.log("A message was received", message);

       this.state.messages.push(message.data.text);
       this.setState({ messages: this.state.messages });
    });

    console.log("You are subscribed");
  }

  async componentWillUnmount() {
    this.channel.unsubscribe();

    console.log("You are unsubscribed");
  }

  sendMessage() {
    this.channel.publish({ name: "myEvent", data: { text: "A message." }})
  }

  render() {
    return (
      <main>
        <button onClick={ (e) => this.sendMessage(e) }>Click here to send a message</button>
        <h2>Messages will appear here:</h2>
        <ul>
          {this.state.messages.map((text, index) => (<li key={"item" + index}>{text}</li>))}
        </ul>
      </main>
    )
  }
}


If you pay close attention, you'll notice that we're creating an instance of the Ably.Realtime client in the constructor of the component in a way that could cause an issue in your application - If you were to use this component multiple times in your web application (for example, to render the current price of some stocks for multiple stocks/share prices), what would actually happen is a new Ably connection would be opened for each subscription, quickly using up your connection limit.


By making a subtle change to this component like this...


const client = new Ably.Realtime("ably-api-key-here");

export default class AblyMessageComponent extends React.Component {
  constructor() {
    super();
    this.state = { messages: [] };
  }

  async componentDidMount() {
    this.channel = await client.channels.get("my-cool-channel");

    await this.channel.subscribe(message => {
      console.log("A message was received", message);

      this.state.messages.push(message.data.text);
      this.setState({ messages: this.state.messages });
    });

    console.log("You are subscribed");
}


and defining the client outside of the scope of the React component, the same client instance - and as such - the same web socket connection, would be shared by all instances of that component.

Each component's subscription will still operate independently, but you will not run through your connections if you rendered this component many times on one page.