In this article, youāll learn how to build a to-do list application that allows you to sign in, create and delete a to-do, and add comments to each to-do using React Native and Socket.io.
If you are reading this, you have probably wondered ā I can do it with a Restful API. So why do I need to use Socket.io?We want to make a todo list where the user can create a todo list for other users and let them see the status online without refreshing the page.
Socket.ioĀ is a highly performant JavaScript library that allows us to create real-time, bi-directional communication between web browsers and a Node.js server. It follows the WebSocket protocol and provides better functionalities, such as fallback to HTTP long-polling or automatic reconnection, which enables us to build efficient real-time applications.
Just a quick background about us. Novu is the first open-sourceĀ notification infrastructure. We basically help to manage all the product notifications. It can beĀ In-AppĀ (the bell icon like you have in Facebook āĀ Websockets), Emails, SMSs and so on.
I would be super happy if you could give us a star! And let me also know in the comments ā¤ļø https://github.com/novuhq/novu
Here, youāll learn how to connect the to-do list application to a Socket.io server. In this guide, Iāll be usingĀ ExpoĀ ā a tool that provides an easier way of building React Native applications.
ExpoĀ saves us from the complex configurations required to create a native application with theĀ React Native CLI, making it the easiest and fastest way to build and publish React Native apps.
Ensure you have theĀ Expo CLI, Node.js, andĀ GitĀ installed on your computer. Then, create the project folder and an Expo React Native app by running the code below.
mkdir todolist-appcd todolist-appexpo init app
Expo allows us to create native applications using theĀ ManagedĀ orĀ Bare Workflow. Weāll use the blank Managed Workflow in this tutorial.
? Choose a template: \u203a - Use arrow-keys. Return to submit. ----- Managed workflow -----\u276f blank a minimal app as clean as an empty canvas blank (TypeScript) same as blank but with TypeScript configuration tabs (TypeScript) several example screens and tabs using react-navigation and TypeScript ----- Bare workflow ----- minimal bare and minimal, just the essentials to get you started
Install Socket.io Client API to the React Native app.
cd appexpo install socket.io-client
Create aĀ socket.jsĀ file within a utils folder.
mkdir utilstouch socket.js
Then, copy the code below into theĀ socket.jsĀ file.
Here, I will guide you through creating the Socket.io Node.js server for real-time communication.
Create aĀ serverĀ folder within the project folder.
cd todolist-appmkdir server
Navigate into theĀ serverĀ folder and create aĀ package.jsonĀ file.
cd server & npm init -y
Install Express.js, CORS, Nodemon, andĀ Socket.ioĀ Server API.
npm install express cors nodemon socket.io
ExpressĀ is a fast, minimalist framework that provides several features for building web applications in Node.js.Ā CORSĀ is a Node.js package that allows communication between different domains.
NodemonĀ is a Node.js tool that automatically restarts the server after detecting file changes, andĀ Socket.ioĀ allows us to configure a real-time connection on the server.
Create anĀ index.jsĀ file ā the entry point to the Node.js server.
touch index.js
Set up a simple Node.js server using Express.js. The code snippet below returns a JSON object when you visit theĀ http://localhost:4000/apiĀ in your browser.
Next, add Socket.io to the project to create a real-time connection. Before theĀ app.get()Ā block, copy the code below.
//\ud83d\udc47\ud83c\udffb New imports.....const socketIO = require('socket.io')(http, { cors: { origin: "http://localhost:3000" }});//\ud83d\udc47\ud83c\udffb Add this before the app.get() blocksocketIO.on('connection', (socket) => { console.log(`\u26a1: ${socket.id} user just connected!`); socket.on('disconnect', () => { socket.disconnect() console.log('\ud83d\udd25: A user disconnected'); });});
From the code snippet above, theĀ socket.io("connection")Ā function establishes a connection with the React app, creates a unique ID for each socket, and logs the ID to the console whenever you refresh the app.
When you refresh or close the app, the socket fires the disconnect event showing that a user has disconnected from the socket.
Configure Nodemon by adding the start command to the list of scripts in theĀ package.jsonĀ file. The code snippet below starts the server using Nodemon.
//\ud83d\udc47\ud83c\udffb In server/package.json"scripts": { "test": "echo \\"Error: no test specified\\" && exit 1", "start": "nodemon index.js" },
You can now run the server with Nodemon by using the command below.
In this section, weāll create the user interface for the to-do list application to enable users to sign in to the application, create and delete to-dos, and add comments to each to-do.
First, letās set up React Navigation.
Create a screens folder within the app folder, and add the Home, Login, and Comments components. Render a āHello Worldā text within them.
Copy the code below into theĀ App.jsĀ file within the app folder.
//\ud83d\udc47\ud83c\udffb the app componentsimport Home from "./screens/Home";import Comments from "./screens/Comments";import Login from "./screens/Login";//\ud83d\udc47\ud83c\udffb React Navigation configurationsimport { NavigationContainer } from "@react-navigation/native";import { createNativeStackNavigator } from "@react-navigation/native-stack";const Stack = createNativeStackNavigator();export default function App() { return ( </Stack.Navigator> </NavigationContainer> );}
The code snippet accepts the username from the user and logs it on the console.
Next, update the code and save the username usingĀ Async StorageĀ for easy identification.
Async StorageĀ is a React Native package used to store string data in native applications. It is similar to the local storage on the web and can be used to store tokens and data in string format.
From the code snippet above, we imported two components,Ā Todo, andĀ ShowModalĀ as sub-components within the Home component. Next, letās create theĀ TodoĀ andĀ ShowModalĀ components.
touch Todo.js ShowModal.js
Update theĀ Todo.jsĀ file to contain the code below. It describes the layout for each to-do.
import { View, Text, StyleSheet } from "react-native";import { React } from "react";import { AntDesign } from "@expo/vector-icons";const Todo = ({ item }) => { return ( {item.title}</Text> View comments</Text> </View> </View> </View> );};export default Todo;
Update theĀ ShowModal.jsĀ file to contain the code below:
Import socket from theĀ socket.jsĀ file into theĀ ShowModal.jsĀ file.
import socket from "../utils/socket";
Update theĀ handleSubmitĀ function to send the new to-do to the server.
//\ud83d\udc47\ud83c\udffb Within ShowModal.jsconst handleSubmit = () => { if (input.trim()) { //\ud83d\udc47\ud83c\udffb sends the input to the server socket.emit("addTodo", input); setVisible(!visible); }};
Create a listener to theĀ addTodoĀ event on the server that adds the to-do to an array on the backend.
//\ud83d\udc47\ud83c\udffb array of todosconst todoList = [];//\ud83d\udc47\ud83c\udffb function that generates a random string as IDconst generateID = () => Math.random().toString(36).substring(2, 10);socketIO.on("connection", (socket) => { console.log(`\u26a1: ${socket.id} user just connected!`); //\ud83d\udc47\ud83c\udffb listener to the addTodo event socket.on("addTodo", (todo) => { //\ud83d\udc47\ud83c\udffb adds the todo to a list of todos todoList.unshift({ _id: generateID(), title: todo, comments: [] }); //\ud83d\udc47\ud83c\udffb sends a new event containing the todos socket.emit("todos", todoList); }); socket.on("disconnect", () => { socket.disconnect(); console.log("\ud83d\udd25: A user disconnected"); });});
TheĀ todosĀ event is triggered only when you create a new to-do. Next, create a route on the server that returns the array of to-dos so you can fetch them via API request within the app.
Update theĀ index.jsĀ file on the server to send the to-do list via an API route as below.
From the image below, there is a delete icon beside each to-do. When you press the button, the selected todo should be deleted on both the server and within the app.
Navigate to theĀ Todo.jsĀ file and import Socket.io.
import socket from "../utils/socket";
Create a function āĀ deleteTodoĀ that accepts the to-do id when you press the delete icon and sends it to the server.
import { View, Text, StyleSheet } from "react-native";import { React } from "react";import { AntDesign } from "@expo/vector-icons";import { useNavigation } from "@react-navigation/native";import socket from "../utils/socket";const Todo = ({ item }) => { const navigation = useNavigation(); //\ud83d\udc47\ud83c\udffb deleteTodo function const deleteTodo = (id) => socket.emit("deleteTodo", id); return ( {item.title}</Text> navigation.navigate("Comments", { title: item.title, id: item._id, }) } > View comments </Text> </View> deleteTodo(item._id)} /> </View> </View> );};export default Todo;
Delete the to-do via its ID.
socket.on("deleteTodo", (id) => { let result = todoList.filter((todo) => todo._id !== id); todoList = result; //\ud83d\udc47\ud83c\udffb sends the new todo list to the app socket.emit("todos", todoList);});
The navigation function accepts the title and id of the selected to-do as parameters; because we want the to-do title at the top of the route and also fetch its comments from the server via its ID.
To achieve this, update theĀ useLayoutEffectĀ hook within theĀ Comments.jsĀ file to change the routeās title and send the ID to the server.
useLayoutEffect(() => { //\ud83d\udc47\ud83c\udffb update the screen's title navigation.setOptions({ title: route.params.title, }); //\ud83d\udc47\ud83c\udffb sends the todo's id to the server socket.emit("retrieveComments", route.params.id); getUsername();}, []);
Listen to theĀ retrieveCommentsĀ event and return the to-doās comments.
socket.on("retrieveComments", (id) => { let result = todoList.filter((todo) => todo._id === id); socket.emit("displayComments", result[0].comments);});
Add anotherĀ useLayoutEffectĀ hook within theĀ Comments.jsĀ file that updates the comments when it is retrieved from the server.
Create the event listener on the server and add the comment to the list of comments.
socket.on("addComment", (data) => { //\ud83d\udc47\ud83c\udffb Filters the todo list let result = todoList.filter((todo) => todo._id === data.todo_id); //\ud83d\udc47\ud83c\udffb Adds the comment to the list of comments result[0].comments.unshift({ id: generateID(), title: data.comment, user: data.user, }); //\ud83d\udc47\ud83c\udffb Triggers this event to update the comments on the UI socket.emit("displayComments", result[0].comments);});
Congratulations!š„Ā Youāve completed the project for this tutorial.
So far, youāve learnt how to set up Socket.io in a React Native and Node.js application, save data withĀ Async Storage, and communicate between a server and the Expo app via Socket.io.
This project is a demo of what you can build using React Native and Socket.io. Feel free to improve the project by using an authentication library and a database that supports real-time storage.
If you feel like this article helped you understand WebSockets better! I would be super happy if you could give us a star! And let me also know in the comments ā¤ļø https://github.com/novuhq/novu