YouTube Icon

Code Playground.

How to Create Chat App Using WebSockets in React/Redux App

CFG

How to Create Chat App Using WebSockets in React/Redux App

Introduction 

WebSockets are a helpful method to make a long-running association between a server and a customer. In an occasion where a web customer needs to continually check the server for information, having a REST-based usage could prompt longer surveying occasions and other related complexities. WebSockets give a two-way correspondence stream, permitting pushing of information from the server to the customer. 

In spite of the fact that utilizing WebSockets is very clear, coordinating it into a React+Redux application can be precarious. This guide will utilize a useful guide to investigate various examples of incorporating WebSockets to a React application and talk about the upsides and downsides of each. 

The Chat App 

In the present guide, you will make a fundamental talk application. The application will give the accompanying highlights: 

  • Anybody can make and join another talk room 
  • After making a room, a remarkable code is made with the goal that others can join 
  • Anybody joining the room should initially include a username 
  • On joining a room, the client can see every single past message from the room 
  • Clients in a room can visit in realtime 

To keep the application from being excessively confused, highlights, for example, client confirmation, room posting, and private visits are not executed, however you are free to actualize such highlights to test the educated ideas. 

In the first place, this guide will tell you the best way to make an essential React application that gives highlights 1-4 from the rundown above. Those four highlights needn't bother with WebSockets for usage and should be possible utilizing your regular React instruments. This guide will utilize Redux with Redux Toolkit for state the executives and Axios for API availability. The talk server will bolster both a HTTP REST API for CRUD tasks and WebSockets for attachment related capacities. 

WebSockets are basically utilized for giving reciprocal correspondence between the clients and the server. At the point when a client enters a message in a room, these messages ought to be sent to the server and put away in visit logs with the goal that the clients joining later can see, and ought to be communicated to every other client in the room. 

It tends to be viewed as a long-surveying necessity from the getting client's point of view in light of the fact that the customer needs to continually inquiry the server for new messages. Hence, this is an ideal chance to utilize the qualities of WebSockets. 

The Chat Server 

Regardless, you need the server part for the general talk application to work. 

This guide will quickly take a gander at the structure of the server and the highlights gave by it. The server gives two key APIs. 

REST API 

REST API is a standard API that sends demands over HTTP nonconcurrently from the web application. It underpins two endpoints: 

  • /room?name="game of positions of authority" to make another visit room and return one of a kind code 
  • /room/{unique-id} to confirm a room when given the one of a kind code and send visit log of the room 

WebSocket API 

An attachment based API encourages constant two-way correspondence. WebSockets depend on an occasion based component over a mutual channel instead of individual endpoints. This guide will investigate this in the genuine execution. 

To keep the server straightforward, all information will be put away in memory utilizing essential information structures, permitting you to maintain your attention on the web application side. 

The Basic React Redux App 

Before acquainting the WebSockets with the application, you'll make the remainder of the application and set up Redux. The fundamental application is very basic and it just has two segments: 

  • HomeComponent contains alternatives for making and going into a room 
  • ChatRoomComponent gives a straightforward talk interface 

At that point you'll utilize a solitary reducer to store the talk logs and other stockpiling components. Until you include the WebSockets, the activities for sending and accepting messages won't be utilized. Aside from these two parts, the code will keep the standard Redux examples.

import axios from 'axios';
import { API_BASE } from './config';

export const SEND_MESSAGE_REQUEST = "SEND_MESSAGE_REQUEST"
export const UPDATE_CHAT_LOG = "UPDATE_CHAT_LOG"

// These are our action types
export const CREATE_ROOM_REQUEST = "CREATE_ROOM_REQUEST"
export const CREATE_ROOM_SUCCESS = "CREATE_ROOM_SUCCESS"
export const CREATE_ROOM_ERROR = "CREATE_ROOM_ERROR"


// Now we define actions
export function createRoomRequest(){
    return {
        type: CREATE_ROOM_REQUEST
    }
}

export function createRoomSuccess(payload){
    return {
        type: CREATE_ROOM_SUCCESS,
        payload
    }
}

export function createRoomError(error){
    return {
        type: CREATE_ROOM_ERROR,
        error
    }
}

export function createRoom(roomName) {
    return async function (dispatch) {
        dispatch(createRoomRequest());
        try{
            const response = await axios.get(`${API_BASE}/room?name=${roomName}`)
            dispatch(createRoomSuccess(response.data));
        }catch(error){
            dispatch(createRoomError(error));
        }
    }
}


export const JOIN_ROOM_REQUEST = "JOIN_ROOM_REQUEST"
export const JOIN_ROOM_SUCCESS = "JOIN_ROOM_SUCCESS"
export const JOIN_ROOM_ERROR = "JOIN_ROOM_ERROR"

export function joinRoomRequest(){
    return {
        type: JOIN_ROOM_REQUEST
    }
}

export function joinRoomSuccess(payload){
    return {
        type: JOIN_ROOM_SUCCESS,
        payload
    }
}

export function joinRoomError(error){
    return {
        type: JOIN_ROOM_ERROR,
        error
    }
}

export function joinRoom(roomId) {
    return async function (dispatch) {
        dispatch(joinRoomRequest());
        try{
            const response = await axios.get(`${API_BASE}/room/${roomId}`)
            dispatch(joinRoomSuccess(response.data));
        }catch(error){
            dispatch(joinRoomError(error));
        }
    }
}

export const SET_USERNAME = "SET_USERNAME"

export function setUsername(username){
    return {
        type: SET_USERNAME,
        username
    }
}
import { CREATE_ROOM_SUCCESS, JOIN_ROOM_SUCCESS, SET_USERNAME} from './actions';

const initialState = {
    room: null,
    chatLog: [],
    username: null
}

export default function chatReducer(state, action) {
    if (typeof state === 'undefined') {
        return initialState
    }

    switch(action.type){
        case CREATE_ROOM_SUCCESS:
            state.room = action.payload;
            break;
        
        case JOIN_ROOM_SUCCESS:
            state.room = action.payload;
            break;

        case SET_USERNAME:
            state.username = action.username;
            break;
    
    }

    return state
}
import React, { useState } from 'react';
import logo from './logo.svg';
import './App.css';
import { Provider, useSelector, useDispatch } from 'react-redux'
import store from './store';
import { createRoom, setUsername, joinRoom } from './actions';

function ChatRoom() {
    const [usernameInput, setUsernameInput] = useState("");
    const username = useSelector(state => state.username);
    const dispatch = useDispatch();

    function enterRooom(){
        dispatch(setUsername(usernameInput));
    }

    return (
        <div>
            {!username && 
            <div className="user">
                <input type="text" placeholder="Enter username" value={usernameInput} onChange={(e) => setUsernameInput(e.target.value)} />
                <button onClick={enterRooom}>Enter Room</button>
            </div>  
            }
            {username &&
            <div className="room">
                <div className="history"></div>
                <div className="control">
                    <input type="text" />
                    <button>Send</button>
                </div>
            </div>
            }

        </div>
    )
}

function HomeComponent(){
    const [roomName, setRoomName] = useState("");
    const [roomId, setRoomId] = useState("");
    const currentRoom = useSelector(state => state.room);

    const dispatch = useDispatch();

    return (
            <>
                {!currentRoom && 
                    <div className="create">
                        <div>
                            <span>Create new room</span>
                            <input type="text" placeholder="Room name" value={roomName} onChange={(e) => setRoomName(e.target.value)} />
                            <button onClick={() => dispatch(createRoom(roomName))}>Create</button>
                        </div>
                        <div>
                            <span>Join existing room</span>
                            <input type="text" placeholder="Room code" value={roomId} onChange={(e) => setRoomId(e.target.value)} />
                            <button onClick={() => dispatch(joinRoom(roomId))}>Join</button>
                        </div>
                    </div>  
                }

                {currentRoom && 
                    <ChatRoom />
                }
            </>
    );
}

function App() {
    return (
        <Provider store={store}>
            <div className="App">
                <HomeComponent />
            </div>
        </Provider>
    )
}

export default App;

The app at this stage supports creating a room and joining to it through the unique code. Next, focus on adding WebSockets to the mix.

Including WebSockets 

To encourage attachment interchanges in React, you'll utilize the accepted library socket.io-customer. Utilize the order npm introduce - S socket.io-customer to introduce it. 

There are different methods for adding WebSocket backing to a React application. Every strategy has its advantages and disadvantages. This guide will experience a portion of the regular examples yet will just investigate in detail the example we are executing. 

Segment level Integration 

Right now, could expect the WebSockets part as a different util. You would start an attachment association at the AppComponent init as a singleton and utilize the attachment example to tune in to attachment messages that are important to the specific part. An example execution is as per the following:

import { socket } from 'socketUtil.js';
import { useDispatch } from 'react-redux';

function ChatRoomComponent(){
    const dispatch = useDispatch();

    useEffect(() => {
        socket.on('event://get-message', payload => {
            // update messages
            useDispatch({ type: UPDATE_CHAT_LOG }, payload)
        });
        socket.on('event://user-joined', payload => {
            // handling a new user joining to room
        });
    });
    
    // other implementations
}

As should be obvious over, this totally isolates the Redux and WebSocket executions and gives a fitting and-play design. This strategy is valuable in the event that you are actualizing WebSockets to a couple of parts of a current application, for instance, on the off chance that you have a blog application and you need to give ongoing pop-up messages. All things considered, you just need WebSockets for the notice part and this example would be a spotless method to actualize. Be that as it may, if the application is attachment substantial, this technique would inevitably turn into a weight to create and keep up. The attachment util works freely and doesn't function admirably with React lifecycles. Additionally, the numerous occasion ties in every part would inevitably hinder the whole application. 

React Middleware Integration 

Another mainstream approach is to acquaint WebSockets as a middleware with the store. This splendidly fits the WebSocket's nonconcurrent nature with the single direction information stream example of Redux. In the usage, a WebSocket association would be started in the middleware init, and ensuing attachment occasions would be designated inside to Redux activities. For instance, when the occasion://get-message payload arrives at the customer, the middleware will dispatch the UPDATE_CHAT_LOG activity inside. The reducers would then refresh the store with the following arrangement of messages. While this is an intriguing methodology, it won't be talked about finally right now. For your reference, this article gives astounding bearings on usage. This technique is perfect if a WebSocket is an indispensable piece of the application and firmly coupling with Redux is normal. 

React Context Integration 

The last strategy is the utilization of React Context to encourage WebSocket corReact ence. This guide will experience the execution and afterward talk about why this is favored over the rest. React  Context was presented as a method for dealing with the application state without going down props through the parent-kid trees. With the ongoing presentation of Hooks, utilizing Context got trifling. 

In the first place, you'll make a Context class for WebSockets that introduces the attachment association.

import React, { createContext } from 'react'
import io from 'socket.io-client';
import { WS_BASE } from './config';
import { useDispatch } from 'react-redux';
import { updateChatLog } from './actions';

const WebSocketContext = createContext(null)

export { WebSocketContext }

export default ({ children }) => {
    let socket;
    let ws;

    const dispatch = useDispatch();

    const sendMessage = (roomId, message) => {
        const payload = {
            roomId: roomId,
            data: message
        }
        socket.emit("event://send-message", JSON.stringify(payload));
        dispatch(updateChatLog(payload));
    }

    if (!socket) {
        socket = io.connect(WS_BASE)

        socket.on("event://get-message", (msg) => {
            const payload = JSON.parse(msg);
            dispatch(updateChatLog(payload));
        })

        ws = {
            socket: socket,
            sendMessage
        }
    }

    return (
        <WebSocketContext.Provider value={ws}>
            {children}
        </WebSocketContext.Provider>
    )
}

Note above that two additional functionalities were introduced:

  • Sending socket messages The emit function is encapsulated within functions with definitive names. For example, sendMessage would essentially emit a socket message as follows.
event: events://send-message
payload: <message content>
  • Accepting attachment messages All getting attachment messages are mapped to separate Redux activities. Since the WebSocket setting will be utilized inside the Redux supplier, it would approach the Redux dispatch technique. 

Initially, this usage would appear pointless excess and excessively like the main technique talked about above. In any case, a couple of key contrasts include incredible incentive over the long haul. 

  • The inception of the WebSocket fills in as a piece of the React cycle. On account of attachment disappointment, you could undoubtedly deal with or give input to the client. This can be dealt with midway. 
  • For a given occasion, there might be one occasion official. Since every occasion maps to a Redux activity there are no redundant ties by singular parts. When the application scales, this forestalls a great deal of head scratches. 
  • All attachment activities are enclosed by capacities permitting firm command over the structure of the payload and approvals on the parameters. 
  • All attachment related code is accessible midway at one area. Despite the fact that this is on the experts list, in specific conditions (ex: smaller scale frontends) this could be an issue. Be that as it may, in a greater part of cases, this would be a preferred position. 

With these highlights, the setting based coordination is a solid match for versatility just as the viability of the codebase. Since you have the WebSocketContext made, you can investigate how to utilize it practically.

export const SEND_MESSAGE_REQUEST = "SEND_MESSAGE_REQUEST"
export const UPDATE_CHAT_LOG = "UPDATE_CHAT_LOG"

export function updateChatLog(update){
    return {
        type: UPDATE_CHAT_LOG,
        update
    }
}
// App.js
import WebSocketProvider, { WebSocketContext } from './WebSocket';

// ....

function App() {
    return (
        <Provider store={store}>
            <WebSocketProvider>
                <div className="App">
                    <HomeComponent />
                </div>
            </WebSocketProvider>
        </Provider>
    )
}

function ChatRoom() {
    const [usernameInput, setUsernameInput] = useState("");
    const [msgInput, setMsgInput] = useState("");

    const room = useSelector(state => state.room);
    const username = useSelector(state => state.username);
    const chats = useSelector(state => state.chatLog);

    const dispatch = useDispatch();
    const ws = useContext(WebSocketContext);

    function enterRooom(){
        dispatch(setUsername(usernameInput));
    }

    const sendMessage = () => {
        ws.sendMessage(room.id, {
            username: username,
            message: msgInput
        });
    }

    return (
        <div>
            <h3>{room.name} ({room.id})</h3>
            {!username && 
            <div className="user">
                <input type="text" placeholder="Enter username" value={usernameInput} onChange={(e) => setUsernameInput(e.target.value)} />
                <button onClick={enterRooom}>Enter Room</button>
            </div>  
            }
            {username &&
            <div className="room">
                <div className="history" style={{width:"400px", border:"1px solid #ccc", height:"100px", textAlign: "left", padding: "10px", overflow: "scroll"}}>
                    {chats.map((c, i) => (
                        <div key={i}><i>{c.username}:</i> {c.message}</div>
                    ))}
                </div>
                <div className="control">
                    <input type="text" value={msgInput} onChange={(e) => setMsgInput(e.target.value)} />
                    <button onClick={sendMessage}>Send</button>
                </div>
            </div>
            }

        </div>
    )
}

As appeared over, the utilization of setting based incorporation is spotless. The WebSocket setting can be gotten to anyplace in the application utilizing the useContext Hook, and all the included usefulness will be accessible. Moreover, a genuine world application would likewise need to deal with the occurrences of attachment disengaging and reconnecting, and taking care of customer exit. These occasions can without much of a stretch be included into the WebSocketContext, leaving the remainder of the application clean. 

Conclusion

Right now, talked about the distinctive reasonable ways to deal with coordinating WebSockets in a current React/Redux web application. We quickly sketched out three unique examples and investigated their qualities and shortcomings. Notwithstanding, for an adaptable and viable usage, the React Context-based methodology is liked.




CFG