Building a WebSocket Context in React Native with Socket.IO
Ajmal Hasan
Posted on April 18, 2023
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:
- Important considerations before using WebSockets.
- Setting up a WebSocket context.
- Best practices for using WebSockets in React Native components.
Key Considerations Before Using WebSockets
- Connection Stability: Mobile networks can be unstable, so ensure that your app can handle reconnections gracefully. Use reconnect options or manage reconnection manually.
- Authentication: Secure your WebSocket connection by sending an access token with each connection. Always validate tokens on the server side.
- Connection Cleanup: Always disconnect WebSocket connections when no longer needed (e.g., when a user logs out or navigates away).
- Error Handling: Handle connection errors, such as authentication failures, and try to refresh the token or prompt the user to log in again.
- 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
-
Create WebSocket Context:
WSContext
will store the WebSocket connection and provide utility functions to manage it. -
Define
WSProvider
: This component initializes the WebSocket connection, manages connection state, and provides methods for interacting with the WebSocket. -
Create
useWS
Hook: TheuseWS
hook gives access to WebSocket functions within any component wrapped byWSProvider
.
Here’s how to set up these steps:
Configuration:
import { createContext, useContext, useEffect, useRef, useState } from "react";
import { io } from "socket.io-client";
import { SOCKET_URL } from "./config";
import { tokenStorage } from "@/store/storage"; // Storage utility for tokens
import { refresh_tokens } from "./apiInterceptors"; // Function to refresh tokens
// Create a WebSocket context for managing the socket instance
const WSContext = createContext(undefined);
// WebSocket Provider Component
export const WSProvider = ({ children }) => {
// State to manage the current access token for WebSocket
const [socketAccessToken, setSocketAccessToken] = useState(null);
// Ref to store the WebSocket connection instance
const socket = useRef();
// Retrieve the token from storage on component mount
useEffect(() => {
const token = tokenStorage.getString("access_token");
setSocketAccessToken(token);
}, []);
// Establish and manage the WebSocket connection whenever the token changes
useEffect(() => {
if (socketAccessToken) {
// Disconnect existing socket before creating a new one
if (socket.current) {
socket.current.disconnect();
}
// Create a new WebSocket connection
socket.current = io(SOCKET_URL, {
transports: ["websocket"], // Use WebSocket transport
withCredentials: true, // Send cookies with the request
extraHeaders: {
access_token: socketAccessToken || "", // Pass the token as a header
},
});
// Handle connection errors (e.g., authentication issues)
socket.current.on("connect_error", (error) => {
if (error.message === "Authentication error") {
console.log("Auth connection error: ", error.message);
refresh_tokens(); // Refresh the token if authentication fails
}
});
}
// Cleanup the WebSocket connection on unmount
return () => {
if (socket.current) {
socket.current.disconnect();
}
};
}, [socketAccessToken]);
// Emit an event to the server
const emit = (event, data = {}) => {
socket.current?.emit(event, data);
};
// Listen for an event from the server
const on = (event, cb) => {
socket.current?.on(event, cb);
};
// Remove a specific event listener
const off = (event) => {
socket.current?.off(event);
};
// Remove a specific listener by its name
const removeListener = (listenerName) => {
socket.current?.removeListener(listenerName);
};
// Disconnect the WebSocket connection
const disconnect = () => {
if (socket.current) {
socket.current.disconnect();
socket.current = undefined; // Clear the socket reference
}
};
// Update the access token in the state
const updateAccessToken = () => {
const token = tokenStorage.getString("access_token");
setSocketAccessToken(token);
};
// Define the WebSocket service with all utility functions
const socketService = {
initializeSocket: () => {}, // Placeholder for future initialization logic
emit,
on,
off,
disconnect,
removeListener,
updateAccessToken,
};
// Provide the WebSocket service to child components
return (
<WSContext.Provider value={socketService}>{children}</WSContext.Provider>
);
};
// Custom hook to use the WebSocket service in components
export const useWS = () => {
const socketService = useContext(WSContext);
if (!socketService) {
throw new Error("useWS must be used within a WSProvider");
}
return socketService;
};
USAGE in components:
import React, { useEffect, useState } from "react";
import { WSProvider, useWS } from "./WSProvider"; // Import your WebSocket Provider and hook
import {
View,
Text,
TextInput,
Button,
StyleSheet,
ScrollView,
Alert,
} from "react-native";
const WebSocketExample = () => {
const { emit, on, off, disconnect, updateAccessToken } = useWS();
const [messageToSend, setMessageToSend] = useState("");
const [receivedMessage, setReceivedMessage] = useState("");
// Example: Listening to "message" events
useEffect(() => {
const handleMessage = (data) => {
console.log("Message received:", data);
setReceivedMessage(data?.text || "No content");
};
// Attach listener
on("message", handleMessage);
// Cleanup listener on unmount
return () => {
off("message");
};
}, [on, off]);
// Example: Emit a "chat_message" event
const handleSendMessage = () => {
if (messageToSend.trim()) {
emit("chat_message", { text: messageToSend });
console.log("Message sent:", messageToSend);
setMessageToSend(""); // Clear input
} else {
Alert.alert("Error", "Message cannot be empty!");
}
};
// Example: Update access token
const handleUpdateToken = () => {
console.log("Updating WebSocket token...");
updateAccessToken(); // Refresh or update token
Alert.alert("Token Updated", "The WebSocket token has been updated.");
};
// Example: Disconnect WebSocket
const handleDisconnect = () => {
console.log("Disconnecting WebSocket...");
disconnect();
Alert.alert("Disconnected", "The WebSocket connection has been closed.");
};
return (
<ScrollView contentContainerStyle={styles.container}>
<Text style={styles.title}>WebSocket Example</Text>
{/* Send Message */}
<View style={styles.section}>
<Text style={styles.subtitle}>Send a Message</Text>
<TextInput
value={messageToSend}
onChangeText={setMessageToSend}
placeholder="Type your message..."
style={styles.input}
/>
<Button title="Send Message" onPress={handleSendMessage} />
</View>
{/* Received Message */}
<View style={styles.section}>
<Text style={styles.subtitle}>Received Message</Text>
<Text style={styles.message}>
{receivedMessage || "No messages yet."}
</Text>
</View>
{/* Update Token */}
<View style={styles.section}>
<Text style={styles.subtitle}>Update Access Token</Text>
<Button title="Update Token" onPress={handleUpdateToken} />
</View>
{/* Disconnect WebSocket */}
<View style={styles.section}>
<Text style={styles.subtitle}>Disconnect WebSocket</Text>
<Button title="Disconnect" onPress={handleDisconnect} />
</View>
</ScrollView>
);
};
// Wrap the app with the WebSocket provider
const App = () => {
return (
<WSProvider>
<WebSocketExample />
</WSProvider>
);
};
export default App;
// Styles
const styles = StyleSheet.create({
container: {
flexGrow: 1,
padding: 20,
backgroundColor: "#f9f9f9",
},
title: {
fontSize: 24,
fontWeight: "bold",
textAlign: "center",
marginBottom: 20,
},
section: {
marginBottom: 20,
},
subtitle: {
fontSize: 18,
fontWeight: "bold",
marginBottom: 10,
},
input: {
borderWidth: 1,
borderColor: "#ccc",
borderRadius: 5,
padding: 10,
marginBottom: 10,
},
message: {
fontSize: 16,
color: "#333",
},
});
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.
💖 💪 🙅 🚩
Ajmal Hasan
Posted on April 18, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
programming Top 8 Leading API Architectural Styles Across the Tech Industry (Part 1)
August 16, 2023