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.
Prerequisites:
- Javascript
- React JS
- useState() and useEffect() Hook in React
- Socket.io
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):
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:
// 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:
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):
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
.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;
}
// 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: