react-nativeactioncableruby-on-rails-7

React Native subscribes multiple times to websocket on load


I am new to react native and I am trying to write a small app that connects to a websocket, powered by Rails ActionCable, sends a message and then when I click Disconnect it closes the connection and unsubscribes from the channel.

Here is my code on the react native side:

import React, { useState, useEffect, useCallback } from 'react';
import { View, Text } from 'react-native';
import { Button } from 'react-native-paper';
import { ActionCable, Cable } from '@kesha-antonov/react-native-action-cable';
import UUID from 'react-native-uuid';

const actionCable = ActionCable.createConsumer('wss://blabla.ngrok-free.app/cable');
const cable = new Cable({}) 

const App: React.FC = () => {

  //const actionCable = ActionCable.createConsumer('wss://blabla.ngrok-free.app/cable');
  //const cable = new Cable({}) 

  // const channel = cable.setChannel(
  //   `wire_channel_${identifier}`, // channel name to which we will pass data from Rails app with `stream_from`
  //   actionCable.subscriptions.create({
  //     channel: 'WireChannel', // from Rails app app/channels/chat_channel.rb
  //   })
  // )

  const [isWebsocketConnected, setIsWebsocketConnected] = useState(false);
  const [dchannel, setDchannel] = useState();

  const identifier = UUID.v4();
  console.log(identifier);

  const onNewMessage = useCallback(message => {
  }, [])

  const handleReceived = useCallback(({ type, message }) => {
    console.log(type);
    console.log(message);
    console.log("triggered")
  }, [])

  const handleConnected = useCallback(() => {
    setDchannel(cable.setChannel(
      `wire_channel_${identifier}`, // channel name to which we will pass data from Rails app with `stream_from`
      actionCable.subscriptions.create({
        channel: 'WireChannel', // from Rails app app/channels/chat_channel.rb
      })
    ))  
    setIsWebsocketConnected(true)
  }, [])

  const handleDisconnected = useCallback(() => {
    removeChannel()
    setIsWebsocketConnected(false)
  }, [])

  const handleSendMessage = useCallback(() => {
    const channelName = getChannelName(identifier)
    const channel = cable.channel(channelName)
    dchannel.perform('speak', { message: 'Hey', identifier: identifier })
  })

  const getChannelName = (identifier) => {
    return `wire_channel_${identifier}`
  };


  const removeChannel = useCallback(() => {
    const channelName = getChannelName(identifier)
    console.log("getChannelName: "+channelName);

    const channel = cable.channel(channelName)
    console.log(channel);
    if (!channel)
      return

    channel
      .removeListener( 'received', handleReceived )
      .removeListener( 'connected', handleConnected )
      .removeListener( 'disconnected', handleDisconnected )
    channel.unsubscribe()
    console.log(cable.channels)
    delete( cable.channels[channelName] )
    console.log(cable.channels)
  }, [])

  useEffect(() => {
    // channel.on('received', handleReceived);
    // channel.on('connected', handleConnected);
    // channel.on('disconnected', handleDisconnected);

    return () => {
      if(dchannel){
        dchannel.unsubscribe();
      }
    };
  }, []); 

  return (
    <View style={{ flex: 1 }}>
      <View style={{ padding: 20, backgroundColor: '#f0f0f0', alignItems: 'center' }}>
        <Text style={{ fontSize: 24 }}>Your Logo Here</Text>
      </View>
      <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
        <Button
          mode="contained"
          onPress={handleConnected}
          color="#00C853" // Green color for "Connect" button
          disabled={isWebsocketConnected}
        >
          Connect WebSocket
        </Button>
        <Button
          mode="contained"
          onPress={handleDisconnected}
          color="#FF1744" // Red color for "Disconnect" button
          disabled={!isWebsocketConnected}
        >
          Disconnect WebSocket
        </Button>
        <Button
          mode="contained"
          onPress={handleSendMessage}
          disabled={!isWebsocketConnected}
        >
          Send Message
        </Button>
        <Text>
          WebSocket Status: {isWebsocketConnected ? 'Connected' : 'Disconnected'}
        </Text>
        {/*<Text>Counter: {counter}</Text>
        <View style={{ marginTop: 20 }}>
          <Text>Messages from Server:</Text>
          {messages.map((message, index) => (
            <Text key={index}>{message}</Text>
          ))}
        </View>*/}
      </View>
    </View>
  );
};

export default App;

The problem I have is that when the app loads i see a lot of subscription created on the server side and when I click Send Message, The server broadcasts multiple messages (1 per each subscription created). I want to establish only 1 subscription and that should be only when I click the Connect Websocket button.

How can I achieve this in the code above. I have tried multiple things already.


Solution

  • Please remove the usecallback hook in your code and check the output. The usecallback is not for every function. Please check the

    What is useCallback in React and when to use it?

    for efficient use of usecallback hooks.