How to Integrate WebSockets with React Redux
Integrating WebSockets with Redux allows for real-time bidirectional communication between the client (e.g. a web browser) and the server. This enables applications to push data from the server to the client instantly, facilitating features such as real-time updates, live notifications, and chat applications
Steps to Setup the Backend:
Step 1: Create a new directory for your project and navigate into it in your terminal.
mkdir server
cd server
Step2: Run the following command to initialize a new Node.js project and create a package.json file:
npm init -y
Step 3: Install web socket Dependencies from the given command.
npm install ws
The updated dependencies in package.json file will look like.
"dependencies": {
"ws": "^8.16.0"
}
Example: This example used to setup the backend for the project.
// index.js
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', function connection(ws) {
ws.on('message', function incoming(message) {
console.log('Received:', message);
// Echo back the received message
ws.send(message);
});
});
Output: Run the server with the following command in the terminal
node index.js
Steps to Setup the Frontend
npx create-react-app foldername
Step 2: After creating your project folder i.e. foldername, move to it using the following command:
cd foldername
Step 3: Install required dependencies
npm install react-redux redux redux-thunk
Step 4: After setting up react environment on your system, we can start by creating an App.js file and create a directory by the name of components in which we will write our desired function.
Project Structure:
The updated dependencies in package.json file will look like.
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-redux": "^9.1.0",
"react-scripts": "5.0.1",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
"web-vitals": "^2.1.4"
},
Approach to integrate WebSocket with React Redux:
- Configure Redux store with middleware like
redux-thunk
orredux-saga
for handling asynchronous actions, including WebSocket interactions. - Create a WebSocket instance for bidirectional real-time communication between the client and server.
- Create action creators for WebSocket events, such as sending messages to the server and handling incoming messages.
- Define reducers to update the Redux store based on WebSocket actions, ensuring state predictability and synchronization with server data.
- Dispatch WebSocket actions from components or middleware to initiate WebSocket communications and reflect real-time updates in the UI through Redux-managed state.
Example: Implementation to showcase the process integrating WebSockets with Redux using chat application.
//src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import { thunk } from 'redux-thunk';
import rootReducer from './reducers';
import App from './App';
import { connectWebSocket } from './actions/websocketActions';
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
store.dispatch(connectWebSocket());
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
// App.js
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import {
connectWebSocket,
sendMessage,
receiveMessage
} from './actions/websocketActions';
const App = ({ connectWebSocket,
sendMessage, receiveMessage,
messages }) => {
const [messageText, setMessageText] = useState('');
useEffect(() => {
connectWebSocket();
}, [connectWebSocket]);
useEffect(() => {
const messageChannel =
new BroadcastChannel('chat_messages');
messageChannel.onmessage = (event) => {
// Update messages in this tab
// when a message is received from another tab
receiveMessage(event.data);
};
return () => {
messageChannel.close();
};
}, []);
const handleMessageChange = (e) => {
setMessageText(e.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
if (messageText.trim() !== '') {
sendMessage(messageText);
setMessageText('');
}
};
return (
<div className="App">
<h1>Real-Time Chat Application</h1>
<div className="chat-container">
{messages.map((message, index) => (
<div key={index} className="message">
{message}
</div>
))}
</div>
<form onSubmit={handleSubmit}>
<input
type="text"
value={messageText}
onChange={handleMessageChange}
placeholder="Type your message..."/>
<button type="submit">Send</button>
</form>
</div>
);
};
const mapStateToProps = (state) => ({
messages: state.websocket.messages
});
export default connect(mapStateToProps,
{
connectWebSocket,
sendMessage,
receiveMessage
})(App);
// websocketActions.js
let ws;
let messageChannel;
let isEventListenerSetup = false;
// Generate a unique identifier for each browser
const userId = Math.random().toString(36).substring(7);
export const connectWebSocket = () => (dispatch) => {
// Ensure WebSocket connection is established only once
if (!ws || ws.readyState === WebSocket.CLOSED) {
ws = new WebSocket('ws://localhost:8080');
ws.onopen = () => {
console.log('WebSocket connected successfully!');
};
ws.onmessage = async (event) => {
const message = await event.data.text();
const formattedMessage = `${userId}: ${message}`;
// Broadcast the received message
// to all Broadcast Channel clients
messageChannel.postMessage(formattedMessage);
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
ws.onclose = () => {
console.log('WebSocket connection closed.');
};
// Log the WebSocket object to check
// if it's being created multiple times
console.log('WebSocket:', ws);
}
if (!messageChannel) {
messageChannel = new BroadcastChannel('chat_messages');
}
if (!isEventListenerSetup) {
messageChannel.onmessage = (event) => {
};
isEventListenerSetup = true;
}
dispatch({
type: 'WEBSOCKET_CONNECTED',
payload: ws
});
};
export const sendMessage = (message) => (dispatch) => {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(message);
}
};
export const receiveMessage = (message) => ({
type: 'WEBSOCKET_MESSAGE_RECEIVED',
payload: message
});
// WebSocketComponent.js
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import { connectWebSocket } from '../actions/websocketActions';
const WebSocketComponent = ({ connectWebSocket, messages }) => {
useEffect(() => {
const socket = connectWebSocket();
socket.onopen = () => {
console.log('WebSocket connected successfully!');
};
socket.onerror = (error) => {
console.error('WebSocket error:', error);
};
socket.onclose = () => {
console.log('WebSocket connection closed.');
};
return () => {
socket.close();
};
}, [connectWebSocket]);
return (
<div>
{messages.map((message, index) => {
console.log('Message:', message);
return (
<div
key={index}
style={{
padding: '5px 10px',
margin: '5px',
borderRadius: '5px',
alignSelf: message.source ===
'right' ? 'flex-end' : 'flex-start',
backgroundColor: message.source ===
'right' ? '#d3d3d3' : '#f0f0f0',
}}>
{message.content}
</div>
);
})}
</div>
);
};
const mapStateToProps = (state) => ({
messages: state.websocket.messages,
});
export default connect(mapStateToProps,
{ connectWebSocket })(WebSocketComponent);
//src/store/configureStore.js
import { createStore, applyMiddleware } from 'redux';
import { thunk } from 'redux-thunk';
import rootReducer from '../reducers';
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
export default store;
// reducers/websocketReducer.js
const initialState = {
connection: null,
messages: []
};
const websocketReducer = (state = initialState, action) => {
switch (action.type) {
case 'WEBSOCKET_CONNECTED':
return {
...state,
connection: action.payload
};
case 'WEBSOCKET_MESSAGE_RECEIVED':
return {
...state,
messages: [...state.messages, action.payload]
};
case 'WEBSOCKET_MESSAGE_SENT':
return {
...state,
messages: [...state.messages, `Sent: ${action.payload}`]
};
default:
return state;
}
};
export default websocketReducer;
// src/reducers/index.js
import { combineReducers } from 'redux';
import websocketReducer from './websocketReducer';
export default combineReducers({
websocket: websocketReducer
});
Steps to run the Application:
npm start
Output: Your project will be shown in the URL http://localhost:3000/