Open In App

How to Integrate WebSockets with React Redux

Last Updated : 04 Apr, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

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.

JavaScript
// 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:

Screenshot-2024-03-22-215520

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 or redux-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.

JavaScript
//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')
);
JavaScript
// 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);
JavaScript
// 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
});
JavaScript
// 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);
JavaScript
//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;
JavaScript
// 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;
JavaScript
// 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/

cc



Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads