DEV Community

Cover image for Building a WebSocket Context in React Native with Socket.IO
Ajmal Hasan
Ajmal Hasan

Posted on • Edited on

Building a WebSocket Context in React Native with Socket.IO

Installation ->


Before diving into WebSocket integration in a React Native application, it’s essential to consider a few things to ensure efficient, stable, and secure connections. Here’s an updated guide on using WebSocket with Socket.IO in React Native, including best practices for managing connections effectively.


Building a WebSocket Context in React Native with Socket.IO

In this guide, we’ll cover:

  1. Important considerations before using WebSockets.
  2. Setting up a WebSocket context.
  3. Best practices for using WebSockets in React Native components.

Key Considerations Before Using WebSockets

  1. Connection Stability: Mobile networks can be unstable, so ensure that your app can handle reconnections gracefully. Use reconnect options or manage reconnection manually.
  2. Authentication: Secure your WebSocket connection by sending an access token with each connection. Always validate tokens on the server side.
  3. Connection Cleanup: Always disconnect WebSocket connections when no longer needed (e.g., when a user logs out or navigates away).
  4. Error Handling: Handle connection errors, such as authentication failures, and try to refresh the token or prompt the user to log in again.
  5. Resource Management: Too many listeners or open connections can lead to memory leaks. Unsubscribe from events and close connections when they are no longer in use.

Steps 1-3: Setting Up the WebSocket Context and Provider

  1. Create WebSocket Context: WSContext will store the WebSocket connection and provide utility functions to manage it.
  2. Define WSProvider: This component initializes the WebSocket connection, manages connection state, and provides methods for interacting with the WebSocket.
  3. Create useWS Hook: The useWS hook gives access to WebSocket functions within any component wrapped by WSProvider.

Here’s how to set up these steps:

import React, { createContext, useContext, useEffect, useRef, useState } from "react";
import { io } from "socket.io-client";
import { SOCKET_URL } from "../config";
import { tokenStorage } from "../storage";
import { refresh_tokens } from "../api/apiInterceptors";

// Step 1: Create WebSocket Context
const WSContext = createContext(undefined);

// Step 2: Create WebSocket Provider
export const WSProvider = ({ children }) => {
  const [socketAccessToken, setSocketAccessToken] = useState(null); // optional, if token not required in socket
  const socket = useRef();

  // optional, if token not required in socket
  // Retrieve the token on mount
  useEffect(() => {
    const token = tokenStorage.getString("accessToken");
    setSocketAccessToken(token);
  }, []);

  // Establish WebSocket connection with error handling and cleanup
  useEffect(() => {
    if (socketAccessToken) {
      socket.current = io(SOCKET_URL, {
        transports: ["websocket"],
        withCredentials: true,
        extraHeaders: {
          access_token: socketAccessToken || "",
        },
        reconnection: true, // Automatically attempt reconnection
        reconnectionAttempts: 5, // Limit reconnection attempts
      });

      // Handle connection errors, such as authentication issues
      socket.current.on("connect_error", (error) => {
        if (error.message === "Authentication error") {
          console.log("Auth connection error:", error.message);
          refresh_tokens(); // Attempt token refresh if authentication fails
        }
      });
    }

    // Cleanup WebSocket on unmount
    return () => {
      if (socket.current) {
        socket.current.disconnect();
      }
    };
  }, [socketAccessToken]);

  // Define WebSocket utility functions
  const emit = (event, data = {}) => {
    if (socket.current) {
      socket.current.emit(event, data);
    }
  };

  const on = (event, cb) => {
    if (socket.current) {
      socket.current.on(event, cb);
    }
  };

  const off = (event) => {
    if (socket.current) {
      socket.current.off(event);
    }
  };

  const removeListener = (listenerName) => {
    if (socket.current) {
      socket.current.removeListener(listenerName);
    }
  };

  const disconnect = () => {
    if (socket.current) {
      socket.current.disconnect();
      socket.current = undefined;
    }
  };

  const updateAccessToken = () => {
    const token = tokenStorage.getString("accessToken");
    setSocketAccessToken(token);
  };

  // Define the socket service
  const socketService = {
    emit,
    on,
    off,
    disconnect,
    removeListener,
    updateAccessToken, // optional, if token not required in socket
  };

  // Provide socketService to children components
  return (
    <WSContext.Provider value={socketService}>{children}</WSContext.Provider>
  );
};

// Step 3: Create Custom Hook for WebSocket Usage
export const useWS = () => {
  const socketService = useContext(WSContext);
  if (!socketService) {
    throw new Error("useWS must be used within a WSProvider");
  }
  return socketService;
};
Enter fullscreen mode Exit fullscreen mode

Wrapping Your App with the WebSocket Provider

To make the WebSocket connection accessible throughout your app, wrap the main app component with WSProvider. This setup is typically done in your main entry file (e.g., App.js or index.js).

import React from "react";
import { WSProvider } from "./path/to/WSProvider";
import App from "./App";

const Root = () => (
  <WSProvider>
    <App />
  </WSProvider>
);

export default Root;
Enter fullscreen mode Exit fullscreen mode

Best Practices for Using WebSockets in Components

When integrating WebSocket in components, keep these best practices in mind to prevent memory leaks, maintain efficient connections, and manage state effectively.

Example 1: Sending Messages with emit

Always check if the socket is connected before emitting. Here’s an example that uses emit to send messages.

import React, { useState } from "react";
import { useWS } from "./path/to/WSProvider";
import { Button, TextInput, View } from "react-native";

const SendMessageComponent = () => {
  const { emit } = useWS();
  const [message, setMessage] = useState("");

  const sendMessage = () => {
    if (message.trim()) {
      emit("message", { text: message });
      setMessage(""); // Clear input after sending
    }
  };

  return (
    <View>
      <TextInput
        value={message}
        onChangeText={setMessage}
        placeholder="Type a message"
      />
      <Button title="Send" onPress={sendMessage} />
    </View>
  );
};

export default SendMessageComponent;
Enter fullscreen mode Exit fullscreen mode

Example 2: Listening for Messages with on

When listening for events, clean up the listener to prevent memory leaks. Use on to set up the listener and off to remove it when the component unmounts.

import React, { useEffect, useState } from "react";
import { useWS } from "./path/to/WSProvider";
import { Text, View } from "react-native";

const ReceiveMessageComponent = () => {
  const { on, off } = useWS();
  const [receivedMessage, setReceivedMessage] = useState("");

  useEffect(() => {
    // Set up listener for "message" event
    on("message", (data) => {
      setReceivedMessage(data.text);
    });

    // Clean up listener on unmount
    return () => {
      off("message");
    };
  }, [on, off]);

  return (
    <View>
      <Text>Received Message: {receivedMessage}</Text>
    </View>
  );
};

export default ReceiveMessageComponent;
Enter fullscreen mode Exit fullscreen mode

Example 3: Updating the Access Token

After login or token refresh, call updateAccessToken to update the WebSocket authentication.

import React from "react";
import { useWS } from "./path/to/WSProvider";
import { Button, View } from "react-native";

const UpdateTokenComponent = () => {
  const { updateAccessToken } = useWS();

  return (
    <View>
      <Button title="Update Access Token" onPress={updateAccessToken} />
    </View>
  );
};

export default UpdateTokenComponent;
Enter fullscreen mode Exit fullscreen mode

Example 4: Disconnecting the WebSocket

Ensure to disconnect the WebSocket connection when it’s no longer needed, such as on logout.

import React from "react";
import { useWS } from "./path/to/WSProvider";
import { Button, View } from "react-native";

const DisconnectComponent = () => {
  const { disconnect } = useWS();

  return (
    <View>
      <Button title="Disconnect WebSocket" onPress={disconnect} />
    </View>
  );
};

export default DisconnectComponent;
Enter fullscreen mode Exit fullscreen mode

Summary

By following these practices, you can ensure that your WebSocket connections remain stable, secure, and efficient:

  • Set up and manage WebSocket connections using a centralized context (WSProvider).
  • Implement proper connection management (e.g., reconnection, cleanup).
  • Handle authentication with tokens and error handling for a secure connection.
  • Use WebSocket effectively in components by managing listeners and cleaning up to avoid memory leaks.

This setup is ideal for a real-time React Native application, offering an organized, scalable solution for WebSocket communication.

Top comments (0)