Published on 19.3.2020
Building a real-time to-do app with React is a great way to understand how to manage live updates in a collaborative environment. Whether you are developing a task manager, a collaborative whiteboard, or a simple live notification system, real-time updates add a new layer of interactivity.
In this post, we’ll explore how to use WebSockets in React to create a real-time collaborative to-do list. We’ll walk through:
- Using WebSockets for real-time communication.
- Building a collaborative to-do list that updates instantly for all users.
- Handling optimistic updates to enhance user experience while waiting for the server to respond.
Let’s get started!
What are WebSockets?
WebSockets are a protocol for full-duplex communication channels over a single TCP connection. They allow you to send and receive messages between the client and server instantly, without the need for repeatedly making HTTP requests (as is common with REST APIs).
In a to-do app, WebSockets are particularly useful for ensuring that when one user updates their task list (e.g., adding, editing, or deleting a to-do item), those changes are immediately reflected in the browsers of other users who are connected to the same app.
Setting Up a WebSocket Server
Before we dive into the React client, let’s first set up a WebSocket server. We’ll use Node.js with the ws library to create a simple WebSocket server that broadcasts changes to all connected clients.
1. Install the Required Packages
In your server folder, create a Node.js project and install the necessary dependencies:
npm init -y
npm install ws express
2. Set Up the WebSocket Server
Now, create a simple WebSocket server that will listen for incoming connections and broadcast messages to all connected clients.
// server.js
const express = require('express');
const WebSocket = require('ws');
// Set up Express and WebSocket
const app = express();
const wss = new WebSocket.Server({ noServer: true });
// Broadcast function to send data to all connected clients
const broadcast = (data) => {
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(data));
}
});
};
// Handle WebSocket connections
wss.on('connection', (ws) => {
console.log('A new client connected');
// Send a welcome message to the new client
ws.send(JSON.stringify({ message: 'Welcome to the To-Do app!' }));
// Listen for incoming messages from clients
ws.on('message', (message) => {
console.log('Received:', message);
// Broadcast received data to all connected clients
broadcast(JSON.parse(message));
});
});
// Set up server to handle WebSocket connections
app.server = app.listen(3000, () => {
console.log('Server is running on port 3000');
});
app.server.on('upgrade', (request, socket, head) => {
wss.handleUpgrade(request, socket, head, (ws) => {
wss.emit('connection', ws, request);
});
});
In this setup:
- We use Express to set up a basic server.
- We use ws to create a WebSocket server that allows us to communicate with clients.
- The broadcast function sends messages to all connected clients whenever a change happens.
Setting Up the React Client
Now, let’s move on to building the React front-end that interacts with our WebSocket server.
1. Install WebSocket Client Library
In the React project, install the WebSocket client package:
npm install websocket
2. Create the To-Do App with WebSocket Integration
Create the To-Do App component, which will establish a WebSocket connection and listen for updates.
// App.js
import React, { useState, useEffect } from 'react';
import './App.css';
function App() {
const [todos, setTodos] = useState([]);
const [newTodo, setNewTodo] = useState('');
const [socket, setSocket] = useState(null);
// Set up the WebSocket connection
useEffect(() => {
const ws = new WebSocket('ws://localhost:3000');
setSocket(ws);
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.todos) {
setTodos(data.todos); // Update the todo list when server sends a message
}
};
return () => {
ws.close(); // Clean up the WebSocket connection on unmount
};
}, []);
// Handle adding a new todo
const addTodo = () => {
if (!newTodo) return;
const updatedTodos = [...todos, { id: Date.now(), task: newTodo }];
setTodos(updatedTodos);
// Optimistic update: Update the UI immediately before the server confirms
socket.send(JSON.stringify({ type: 'add', todo: { id: Date.now(), task: newTodo } }));
setNewTodo('');
};
// Handle deleting a todo
const deleteTodo = (id) => {
const updatedTodos = todos.filter(todo => todo.id !== id);
setTodos(updatedTodos);
// Optimistic update: Update the UI immediately before the server confirms
socket.send(JSON.stringify({ type: 'delete', id }));
};
return (
<div className="App">
<h1>Real-Time To-Do App</h1>
<input
type="text"
value={newTodo}
onChange={(e) => setNewTodo(e.target.value)}
placeholder="Add a new task"
/>
<button onClick={addTodo}>Add</button>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
{todo.task}
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</li>
))}
</ul>
</div>
);
}
export default App;
3. Real-Time Updates and Optimistic UI
- Optimistic Updates: In the
addTodo
anddeleteTodo
functions, we update the UI immediately when the user makes changes (before waiting for the server response). This makes the app feel faster and more responsive. - WebSocket Communication: When a user adds or deletes a task, we send the corresponding event (either
add
ordelete
) to the WebSocket server, which broadcasts it to all connected clients.
Handling Optimistic Updates
Optimistic updates are crucial for providing an instant response to user interactions. In the code above, we handle optimistic updates by immediately updating the local state of the to-do list when the user adds or deletes a task, even before receiving confirmation from the server.
This makes the app feel snappy and improves the user experience.
If you want to manage the states with more sophistication (e.g., rollback in case of failure), you could introduce a pending state that keeps track of the action until confirmation is received.
Conclusion
In this tutorial, we’ve built a real-time collaborative to-do app using React and WebSockets. We explored:
- How to use WebSockets for real-time updates in React apps.
- How to build a collaborative to-do list that syncs across all clients in real time.
- How to implement optimistic updates to make the UI more responsive and improve the user experience.
WebSockets are a powerful tool for real-time apps, and integrating them with React allows you to create seamless, live applications. Whether you’re building a collaborative app or a live notification system, WebSockets offer a simple and efficient solution for instant communication.