Open In App

Real-Time Collaborative Editing App using React & WebSockets

Last Updated : 13 Mar, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

This article will explain about real-time collaborative editor which can be updated by multiple users at the same time. It explains how to do it using a web socket for example – socket.io and react hooks such as useState() and useEffect().

Output Preview: Let us have a look at how the final output will look like.

Screenshot-2024-03-07-155711

OUTPUT PREVIEW IMAGE OF COLLABORATIVE EDITOR

Prerequisites:

Approach to Create Real-Time Collaborative editor:

  • Initialize a new node.js project, install express and set up basic express server
  • Install socket.io for real time communication
  • Use socket.io event listeners to handle all connection and disconnection
  • Whenever there is any change in real time editor by client, broadcast it to all other clients
  • Implement event handlers to broadcast formatting changes to all the clients
  • Set up the new react app (steps are given below in this article)
  • Install socket-io.client to connect with the server made using socket.io
  • Implement a UI containing textarea and buttons for formatting and style
  • Use event handlers to manage the content updation in the editor
  • Wait for any updates from server and change the editor content accordingly
  • Update the styles of the editor based on the changes received from the server

Steps to create backend –

Step 1: Create backend folder

mkdir backend

Step 2: Change the directory

cd backend

Step 3: Initialize the express app

npm init -y

Step 4: Make server.js file for backend

In this file import all the dependencies and create the server using socket.io

Step 5: Install the required dependencies for backend

npm install express cors socket.io nodemon

Project Structure(Backend):

Screenshot-2024-03-08-154015

PROJECT STRUCTURE FOR BACKEND

Updated Dependencies in package.json file for backend:

"dependencies": {
    "cors": "^2.8.5",
    "express": "^4.18.3",
    "nodemon": "^3.1.0",
    "socket.io": "^4.7.4"
}

Example: Below is the code example:

Javascript
// server.js (backend)

const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const cors = require('cors');

const app = express();
app.use(cors());

const server = http.createServer(app);
const io = socketIo(server, {
    cors: {
        origin: '*',
        methods: ['GET', 'POST'],
        credentials: true
    }
});     
io.on('connection', (socket) => {
    console.log('New client connected');
    socket.on('edit', (content) => {
        io.emit('updateContent', content);
    });

    socket.on('bold', (bold) => {
        io.emit('updateStyleBold', bold);
    })

    socket.on('italic', (italic) => {
        io.emit('updateStyleItalic', italic);
    })

    socket.on('underline', (underline) => {
        io.emit('updateStyleUnderline', underline);
    })

    socket.on('disconnect', () => {
        console.log('Client disconnected');
    });
});

const PORT = 5000;
server.listen(PORT, () => console.log(`Server running on port ${PORT}`));

Steps to run backend:

nodemon server.js
or
node server.js

Output:

Screenshot-2024-03-08-155659

OUTPUT OF BACKEND CODE

Steps to Create Frontend –

Step 1: Create react app

npx create-react-app frontend

Step 2: Change the directory

cd frontend

Step 3: Install all the dependencies

npm install socket-io.client

Project Structure(Frontend):

Screenshot-2024-03-08-155150

PROJECT STRUCTURE FOR FRONTEND

Updated Dependencies in package.json for frontend –

"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-scripts": "5.0.1",
    "socket.io-client": "^4.7.4"
},

Example: Below is the code example

CSS
.App {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 20px;
    font-family: Arial, sans-serif;
}

h1 {
    margin-bottom: 20px;
}

.controls {
    display: flex;
    margin-bottom: 20px;
}

.controls button {
    margin-right: 10px;
    padding: 5px 10px;
    border: none;
    border-radius: 5px;
    background-color: darkslategray;
    color: white;
    cursor: pointer;
}

.controls button:hover {
    background-color: darkslategrey;
}

.controls select,
.controls input[type="color"] {
    margin-right: 10px;
    padding: 5px;
    border: 1px solid #ccc;
    border-radius: 5px;
}

.controls select:focus,
.controls input[type="color"]:focus {
    outline: none;
    border-color: #007bff;
}

textarea {
    width: 100%;
    padding: 10px;
    border: 1px solid #ccc;
    border-radius: 5px;
    font-size: 16px;
    resize: vertical;
}

textarea:focus {
    outline: none;
    border-color: cadetblue;
}
Javascript
// App.js (frontend)

import React,
{
    useState,
    useEffect
} from 'react';
import io from 'socket.io-client';
import './App.css';

const socket = io('http://localhost:5000');

function App() {
    const [content, setContent] = useState('');
    const [bold, setBold] = useState(false);
    const [italic, setItalic] = useState(false);
    const [underline, setUnderline] = useState(false);
    useEffect(() => {
        socket.on('updateContent',
            (updatedContent) => {
                setContent(updatedContent);
            });

        socket.on('updateStyleBold',
            (bold) => {
                setBold(bold);
            });

        socket.on('updateStyleItalic',
            (italic) => {
                setItalic(italic);
            });

        socket.on('updateStyleUnderline',
            (underline) => {
                setUnderline(underline);
            });

        return () => {
            socket.off('updateContent');
        };
    }, [bold, italic, underline]);

    const handleEdit = (event) => {
        const updatedContent = event.target.value;
        setContent(updatedContent);
        socket.emit('edit', updatedContent);
    };

    const handleBold = () => {
        if (!bold) {
            setBold(true);
            socket.emit('bold', true);
        }
        else {
            setBold(false);
            socket.emit('bold', false);
        }
    }

    const handleItalic = () => {
        if (!italic) {
            setItalic(true);
            socket.emit('italic', true);
        }
        else {
            setItalic(false);
            socket.emit('italic', false);
        }
    }

    const handleUnderline = () => {
        if (!underline) {
            setUnderline(true);
            socket.emit('underline', true);
        }
        else {
            setUnderline(false);
            socket.emit('underline', false);
        }
    }


    return (
        <div className="App">
            <h1>Real-time Collaborative Editor</h1>
            <div className='controls'>
                <button onClick={() => handleBold()}>
                    BOLD
                </button>
                <button onClick={() => handleItalic()}>
                    ITALIC
                </button>
                <button onClick={() => handleUnderline()}>
                    UNDERLINE
                </button>
            </div>
            <textarea
                value={content}
                onChange={handleEdit}
                rows={10}
                cols={50}
                style={{
                    fontWeight: bold ? 'bold' : 'normal',
                    fontStyle: italic ? 'italic' : 'normal',
                    textDecoration: underline ? 'underline' : 'none'
                }}
            />
        </div>
    );
}

export default App;

Steps to Run Frontend:

npm start

Output:

outputgif

OUTPUT GIF FOR REAL-TIME COLLABORATIVE EDITOR



Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads