Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

useSyncExternalStore for messages not supported #84

Closed
vukovinski opened this issue Apr 25, 2023 · 1 comment
Closed

useSyncExternalStore for messages not supported #84

vukovinski opened this issue Apr 25, 2023 · 1 comment
Labels
bug Something isn't working

Comments

@vukovinski
Copy link

General

Hello, I'm not sure if this is a problem with the library or with my code, but I feel like the React 18 hook useSyncExternalStore is not supported on the message prop of the Chat component.

What bug do you experience? 🐞

I have a service which provides data for the Conversation component, in the Conversation component I use the react-native-chat-ui Chat component to render the chat. In my first iteration I used React's state hook to supply the messages to the Chat component, but as I have been refining the design I wanted to refactor out the server communication into a separate piece of code. I evaluated that the useSyncExternalStore should do the trick. After rolling out some code for the custom store and trying it out, I'm getting an infinite loop in the React render.

Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.

How can it be reproduced? 🤔

Step 1. Define a service to be used with useSyncExternalStore
Step 2. Use the defined service in a component that renders the Chat component
Step 4. Pass the service data to the Chat messages prop.

What behavior is expected? 💡

The Chat component should use the passed prop as the source of the displayed messages and rerender when the external store calls the listener callback.

// ConversationService.ts

export const conversationService = (id: string) => {
  let messages : Message[] = [];
  let messagesListeners : any[] = [];
  let lastMessagesResponse : GetMessagesResponse;  

  const updateMessages = () => {
    const toTimestamp = new Date();
    const fromTimestamp = lastMessagesResponse?.latestMessageTimestamp ?? new Date(0);
    Server.Conversations.GetMessagesBetween(id, fromTimestamp, toTimestamp)
      .then(resp => {
        messages = [...messages, ...resp.selectedMessages]
        lastMessagesResponse = resp ?? lastMessagesResponse;
        emitMessagesChanged();
      })
      .catch(err => console.error(err));
  }

  const subscribeMessages = (listener: any) => {
    messagesListeners = [...messagesListeners, listener];
    return () => {
      messagesListeners = messagesListeners.filter(l => l !== listener);
    }
  }

  const getMessagesSnapshot = () => {
    return messages?.map(mapMessage) ?? [];
  }

  const emitMessagesChanged = () => {
    for (const listener of messagesListeners) {
      listener();
    }
  }

  const sendTextMessage = (message: MessageType.PartialText) => {
    Server.Conversations.PostMessage(id, { encryptedTextData:  message.text })
      .then(_ => updateMessages())
      .catch(err => console.error(err));
  }

  const mapMessage = (message: Message): MessageType.Any => {
    if (message.encryptedTextData) {
      return {
        id: message.timestamp.valueOf().toString(), // WARN: temporary hack, should return id with message dto
        type: "text", text: message.encryptedTextData,
        createdAt: message.timestamp.valueOf(), author: { id: message.sender.phoneNumber }
      }; 
    }
  }

  return {
    messages: {
      subscribe: subscribeMessages ,
      snapshot: getMessagesSnapshot 
    }
  }
}
// Conversation.tsx

const Conversation = ({ route, navigation }: ConversationProps) => {
  const id = route.params.convId;
  const service = conversationService(id);
  const conversationMessages = useSyncExternalStore<MessageType.Any[]>(service.messages.subscribe, service.messages.snapshot);

  return (
    <View style={{ flex: 1 }}>
       <Chat messages={conversationMessages} />
    </View>
  );
}

Environment info

Please specify the react, react-native, react-native-chat-ui versions.

react: 18.2.0
react-native: 0.71.6
react-native-chat-ui: 1.4.3
expo: 48.0.11

npx react-native info output 👇

System:
    OS: Windows 10 10.0.22623
    CPU: (12) x64 Intel(R) Core(TM) i5-10400 CPU @ 2.90GHz
    Memory: 35.52 GB / 63.82 GB
  Binaries:
    Node: 16.16.0 - C:\Program Files\nodejs\node.EXE
    Yarn: 1.22.19 - ~\AppData\Roaming\npm\yarn.CMD
    npm: 9.6.4 - C:\Program Files\nodejs\npm.CMD
    Watchman: Not Found
  SDKs:
    Android SDK: Not Found
    Windows SDK:
      AllowDevelopmentWithoutDevLicense: Enabled
      Versions: 10.0.17763.0, 10.0.19041.0
  IDEs:
    Android Studio: Not Found
    Visual Studio: 17.5.33530.505 (Visual Studio Community 2022)
  Languages:
    Java: 11.0.16.1 - C:\Program Files\Microsoft\jdk-11.0.16.101-hotspot\bin\javac.EXE
  npmPackages:
    @react-native-community/cli: Not Found
    react: 18.2.0 => 18.2.0
    react-native: 0.71.6 => 0.71.6
    react-native-windows: Not Found
  npmGlobalPackages:
    *react-native*: Not Found

Platform

Expo Go (managed React Native app)

@vukovinski vukovinski added the bug Something isn't working label Apr 25, 2023
@vukovinski
Copy link
Author

I've confirmed that it isn't a problem with the library, thanks for bearing with me!

The issue is in the server/service code somewhere.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

1 participant