GetStream / stream-chat-react

React Chat SDK ➜ Stream Chat 💬
https://getstream.io/chat/sdk/react/
Other
702 stars 273 forks source link

`watch_count` seems to be not updating properly #207

Closed iamacatperson closed 4 years ago

iamacatperson commented 4 years ago

I am facing two issues with watch_count.

  1. Using the react <MessageHeader> component, when a new member joins, the watch_count value only updates when the new member has typed something in the channel. It doesn't update automatically when a new member joins.
  2. watcher_count (in const { watcher_count } = this.channel.state) returns 0 if used outside the <Chat> component.

Please see below code structure:

import React, { Component } from "react";

import {
  Chat,
  Channel,
  Window,
  MessageList,
  MessageInput,
  MessageLivestream,
  ChannelHeader,
  Thread
} from "stream-chat-react";
import { StreamChat } from "stream-chat";

class ChatComponent extends Component {
  constructor(props) {
    super(props);

    this.user = // some user object from local storage //
    this.chatClient = new StreamChat(process.env.STREAM_API_KEY); 
    this.chatClient.setUser(
      {
        id: this.user.id,
        name: this.user.name
      },
      OUR_STREAM_TOKEN
    );
    this.channel = this.chatClient.channel(
      "livestream",
      process.env.STREAM_CHANNEL_ID
    );

    this.channel.watch();
  }

  state = {
    viewers: []
  };

  componentDidMount() {
    this.getViewers(["1", "2", "3"]); // just a list of user IDs

    this.chatClient.on("user.presence.changed", event => {
      this.getViewers(["1", "2", "3"]);
    });
  }

  componentWillUnmount() {
    this.chatClient.off("user.presence.changed");
  }

  getViewers = async viewerIds => {
    const response = await this.chatClient.queryUsers(
      { id: { $in: viewerIds } },
      { last_active: -1 },
      {
        presence: true,
        limit: 10,
        offset: 0
      }
    );

    this.setState({ viewers: response.users });
  };

  render() {
    const { watcher_count } = this.channel.state;

    console.log(watcher_count); // it shows as 0

    return (
      <div className="chat">
        {/** i need to display the count outside the <Chat> component */}
        <div className="chat__header">{watcher_count}</div>{" "}
        <Chat client={this.chatClient} theme={"livestream light"}>
          <Channel channel={this.channel} Message={MessageLivestream}>
            <ChannelHeader live></ChannelHeader>{" "}
            {/** within here, it shows the actual count */}
            <Window hideOnThread>
              <MessageList></MessageList>
              <MessageInput />
            </Window>
            <Thread fullWidth />
          </Channel>
        </Chat>
      </div>
    );
  }
}

export default ChatComponent;

Am I missing anything?

iamacatperson commented 4 years ago

Any update on this?

vishalnarkhede commented 4 years ago

@iamacatperson you should move chat initialization logic to async componentDidMount!!


import React, { Component } from "react";

import {
  Chat,
  Channel,
  Window,
  MessageList,
  MessageInput,
  MessageLivestream,
  ChannelHeader,
  Thread
} from "stream-chat-react";
import { StreamChat } from "stream-chat";

class ChatComponent extends Component {
  state = {
    chatClient: null,
    channel: null;
    viewers: []
  };

  async componentDidMount() {
    const user = // some user object from local storage //
    const chatClient = new StreamChat(process.env.STREAM_API_KEY); 
    await chatClient.setUser(
      {
        id: user.id,
        name: user.name
      },
      OUR_STREAM_TOKEN
    );

    const channel = this.chatClient.channel(
      "livestream",
      process.env.STREAM_CHANNEL_ID
    );

    await channel.watch();

    this.setState({
        chatClient,
        channel
    });
  }

  componentWillUnmount() {
    this.chatClient.off("user.presence.changed");
  }

  getViewers = async viewerIds => {
    const response = await this.chatClient.queryUsers(
      { id: { $in: viewerIds } },
      { last_active: -1 },
      {
        presence: true,
        limit: 10,
        offset: 0
      }
    );

    this.setState({ viewers: response.users });
  };

  render() {
    if (!this.state.chatClient) return null;

    const { watcher_count } = this.state.channel.state;

    return (
      <div className="chat">
        {/** i need to display the count outside the <Chat> component */}
        <div className="chat__header">{watcher_count}</div>{" "}
        <Chat client={this.state.chatClient} theme={"livestream light"}>
          <Channel channel={this.state.channel} Message={MessageLivestream}>
            <ChannelHeader live></ChannelHeader>{" "}
            {/** within here, it shows the actual count */}
            <Window hideOnThread>
              <MessageList></MessageList>
              <MessageInput />
            </Window>
            <Thread fullWidth />
          </Channel>
        </Chat>
      </div>
    );
  }
}

export default ChatComponent;

NOTE I haven't exactly tested the code, but you get the idea I suppose :)

iamacatperson commented 4 years ago

@vishalnarkhede I tried the above implementation and it solves the issue with watcher_count value showing up as 0. I can now see the actual value on page load. However, the watcher count still doesn't update automatically whenever a new user / member joins the channel. The person needs to type something on the chat first, then the watcher_count updates.

As you can see, I have this implementation in the componentDidMount() method:

this.state.chatClient.on("user.presence.changed", event => {
      console.log(event);
     // also, update online / offline user listing
});

this.state.channel.on(event => {
      console.log("event", event);
});

When someone logs-in/out of the application, user.presence.changed is detected, and I can see the log for that event in the console that a person became "offline/online". Hence, am able to show in real-time which people are online/offline. However, channel doesn't detect any change / log anything. The watcher_count remains the same (not real-time).

What could be the issue for that?

iamacatperson commented 4 years ago

I also do not understand this part in the docs under "Watcher Count" (seems to have been updated recently):

let channel = Client.shared.channel(type: .messaging, id: "general")
let subscription = channel.subscribeToWatcherCount { count in
    // handle count
}
// Cancel subscription when you want to stop receiving events
subscription.cancel()

I am getting Unhandled Rejection (TypeError): Cannot read property 'channel' of undefined. There seems to be no shared prop in client object. And also the let subscription part seems to have typo?

vishalnarkhede commented 4 years ago

let channel = Client.shared.channel(type: .messaging, id: "general") let subscription = channel.subscribeToWatcherCount { count in // handle count } // Cancel subscription when you want to stop receiving events subscription.cancel()

@iamacatperson this is part of swift doc. Apologies for confusion, we will fix that asap. Please don't use it for js.

When someone logs-in/out of the application, user.presence.changed is detected, and I can see the log for that event in the console that a person became "offline/online". Hence, am able to show in real-time which people are online/offline. However, channel doesn't detect any change / log anything. The watcher_count remains the same (not real-time).

This is expected since react component is not aware of changes inside channel object. So you need to explicitly call setState here:

this.state.chatClient.on("user.presence.changed", event => {
    this.setState({
        channel: this.state.channel
    })
});
iamacatperson commented 4 years ago

@vishalnarkhede The watcher count still remains unchanged when I console.log the channel state on presence change.

this.state.chatClient.on("user.presence.changed", event => {
    console.log(this.state.channel); // shows the unchanged/old watcher_count
    this.setState({
        channel: this.state.channel
    })
});

Is there any other way?

vishalnarkhede commented 4 years ago

Hey @iamacatperson I missed one thing in my last reply (sorry for that)

Actually you are using wrong event for watcher_count. user.presence.changed event means, some user has established a websocket connection. It doesn't mean he has started watching a channel.

To check for watch, you need user.watching.start event: So for your case, it would be

this.state.channel.on("user.watching.start", event => {
    this.setState({
        channel: this.state.channel
    })
});
vishalnarkhede commented 4 years ago

watcher_count is the number of users who are currently watching the particular channel. Its a subset of number of members who are currently online :)

iamacatperson commented 4 years ago

@vishalnarkhede I tried adding:

this.state.channel.on("user.watching.start", event => {
    console.log(event);
    console.log(this.state.channel)
    this.setState({
        channel: this.state.channel
    })
});

...And the event is never triggered / logged when someone joins. What part of the code actually triggers a user watch event? Am I missing that in my code (in my first post)?

vishalnarkhede commented 4 years ago

@iamacatperson this event gets triggered when user on other end does channel.watch()

iamacatperson commented 4 years ago

@vishalnarkhede Yes, I have that in componentDidMount().

async componentDidMount() {
    const user = // user object //

    const chatClient = new StreamChat(envs.STREAM_API_KEY);

    await chatClient.setUser(
      {
        id: user.id,
        name: user.name
      },
      OUR_STREAM_TOKEN
    );

    const channel = chatClient.channel("livestream", STREAM_CHANNEL_ID);

    await channel.watch();

    this.setState(
      {
        chatClient,
        channel
      }
    );

    this.state.chatClient.on("user.presence.changed", event => {
      // console.log(event);
      // console.log(this.state.channel);
    });

    this.state.channel.on("user.watching.start", event => {
      console.log("channel event");
      console.log(event); // nothing is triggered
      console.log(this.state.channel); 
      this.setState({
        channel: this.state.channel
      });
    });
  }

Still the watch event is not triggered. What else could be the issue?

vishalnarkhede commented 4 years ago

Can you check your chat config on stream dashboard?

Screenshot 2020-04-17 at 13 05 56
vishalnarkhede commented 4 years ago

@iamacatperson missed an important thing here. You need to request presence explicitly

await channel.watch({ presence: true });

https://getstream.io/chat/docs/presence_format/?language=js#listening-to-presence-changes

iamacatperson commented 4 years ago

It finally worked. 👍

I haven't added the ({ presence: true }); yet but previously "Search" and "Connect Events" were off. It didn't occur to me that it's a 'connection' event. Thanks a lot @vishalnarkhede.

vishalnarkhede commented 4 years ago

@iamacatperson glad that it worked :)

iamacatperson commented 4 years ago

Yeah I think it'd be good to add in the documentation to make sure 'connection events' are turned on in the settings. That was the only missing piece, I think.

flybayer commented 4 years ago

The JS documentation still has the Swift code. I was very confused until I found this issue