stream-chat-react-native icon indicating copy to clipboard operation
stream-chat-react-native copied to clipboard

How can I reflect membership state updates?

Open Liamandrew opened this issue 8 months ago • 3 comments

Hi,

I have a scenario where users can view channels they aren't a member of.

When viewing a channel they aren't a member of, I show a "Subscribe" button which should add themselves to the channel. Conversely, if they are a member of said channel then there is an "Unsubscribe" button which removes themselves from the channel.

To drive this logic to determine which button to show, I'm using channel.state.membership?.status. However, because I'm also using context to store the channel/thread (similar to this) between the Channel List and Channel Messages, then I need a way to reflect the membership change back to this context in order for the buttons to switch.

For example, this is what my subscribe action looks like:

const subscribe = async () => {
  await channel.addMembers([user.id]);

  // Fetch the latest channel data and state
  const [nextChannel] = await client.queryChannels({
    cid: channel.cid,
  });

  if (nextChannel) {
    // Update the context so that the channel new membership is reflected
    setChannel(nextChannel);
  }
}

It seems that React ignores the setChannel(nextChannel) call because it deems there to be no change between channel and nextChannel in this case (due to shallow comparison).

So what I've had to do is add a hack to force the screen to re-render by extending the context such that I can force a re-render:

setChannel(nextChannel);
// Update the same context as above
setForceUpdate((prev) => prev + 1);

Is there a recommended way to do this that I'm missing, without needing this force update workaround?

Liamandrew avatar Mar 18 '25 05:03 Liamandrew

Hey @Liamandrew, can you try using our hook useChannelMembershipState for the same? This would give you real-time updates of the channel.state.membership. We use it to implement channel pinning/archiving on our side as well. Let us know if it was helpful. Thanks 😄

khushal87 avatar Mar 18 '25 09:03 khushal87

Hi @khushal87, thanks - Yes I've seen that hook and I am using it, but I think the problem is more basic than that. Currently I pass in the context channel like so:

const { channel, setChannel } = useChatContext();
const membership = useChannelMembershipState(channel);

Which is fine on the initial render. However, when the membership status is changed, I use setChannel(nextChannel) to attempt to re-render with the latest state but it seems this isn't enough to cause a re-rerender.

This is because React essentially performs the following check: Object.is(channel, nextChannel) and this is returning true, hence no re-render.

So my question is, what is the correct way to call setChannel to ensure React knows it's a new value and will re-render. Or, am I passing the wrong value into useChannelMembershipState

Liamandrew avatar Mar 18 '25 22:03 Liamandrew

Hi @Liamandrew ,

No - that should be correct. The thing is, as you pointed out this won't trigger a rerender implicitly because of 2 things:

  • The channel membership state is not recreated, but rather mutated inline (this is not something React can figure out on its own)
  • The hook is responsible for updating the value of the channel.state.membership whenever that changes; however it does not mean that the entire channel would change - hence your issue

But, let me circle back to the initial thing that is being done - you should not need to queryChannels whenever adding channel members. The button you want to use should depend on channel.state.membership directly, meaning you should be able to use the hook to simply decide when to render which variant of the button.

Additionally, if that's a no-go for you, you can also listen to WS events instead of re-querying every time; that should make things a lot smoother for you.

If you absolutely need a full channel rerender for whatever reason whenever you business logic happens and you don't have another out, a workaround you can use is to pass a key to the Channel component which will pretty much force it to rerender. But please only use this as an absolute last resort as this isn't particularly recommended.

isekovanic avatar Apr 02 '25 09:04 isekovanic

Hey @Liamandrew, I will close the issue since we didn't get any reply from you on this one. I hope the above replies should have helped you already. Feel free to reopen the issue if you see there's some issue or ask your doubts and we will be happy to answer you. Thanks 😄

khushal87 avatar Jul 21 '25 13:07 khushal87