Novu raised $6.6m seed funding

How to

Building a chat app with Socket.io and React Native 🤯

Chats are all around us, from Whatsapp to Facebook and Instagram, almost every platform offers a chat in some kind of variation. In Today's digital world we have all gone mobile! Just before this article I wrote a friend a message on Whatsapp.

Nevo David
Nevo DavidOctober 17, 2022

I wrote before on how to build a chat app with React, it got 1660 likes and 52955 views 🤩

So I have decided to make another one for React Native.

Chats are fun. You send a message to a person or group, and they see it and reply. Simple yet complex.
To develop a chat app you would need to be aware of new messages as soon as they arrive.

In this article, we will do something a little bit different than the previous React tutorial. We will create a sign-in screen where you can enter your name, create groups where people can join and show real-time messages between the group members.

Typing

Getting Live Information From The Server ℹ️

There are two ways to get live information from your server about a new bid:

Use long-polling HTTP request, basically an HTTP request every 5 – 10 seconds to get information about a new bid.

Use an open-socket (Websockets) to get information directly from the server when a new bid arrives.

In this article I will talk about Websockets and specifically on the Node.js library – Socket.io

Socket.io is a popular JavaScript library that allows us to create real-time, bi-directional communication between software applications and a Node.js server.

Novu – the first open-source notification infrastructure

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 the Dev Community – Websockets), Emails, SMSs and so on.

I would be super happy if you could give us a star! It will help me to make more articles every week 🚀
https://github.com/novuhq/novu

Novu
We are sending some awesome swag during Hacktoberfest 😇

How to create a real time connection between React Native & Socket.io

In this tutorial, we’ll build the chat application with Expo – an open-source framework that enables us to create native apps for IOS and Android by writing React and JavaScript code.

Installing Expo

Expo saves us from the complex configurations required to create a native application with React Native CLI, making it the easiest and fastest way to build and publish React Native apps.

Ensure you have the Expo CLINode.js, and Git installed on your computer. Then, create the project folder and an Expo React Native app by running the code below.

1mkdir chat-app
2cd chat-app
3expo 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 because all the necessary configurations have been completed for us.

1? Choose a template: › - Use arrow-keys. Return to submit.
2    ----- Managed workflow -----
3❯   blank               a minimal app as clean as an empty canvas
4    blank (TypeScript)  same as blank but with TypeScript configuration
5    tabs (TypeScript)   several example screens and tabs using react-navigation and TypeScript
6    ----- Bare workflow -----
7    minimal             bare and minimal, just the essentials to get you started

Install Socket.io Client API to the React Native app.

1cd app
2expo install socket.io-client

Create a socket.js within a utils folder and copy the code below into the file

1mkdir utils
2touch socket.js
3//👇🏻 Paste within socket.js file
4
5import { io } from "socket.io-client";
6const socket = io.connect("http://localhost:4000");
7export default socket;

The code snippet above creates a real-time connection to the server hosted at that URL. (We’ll create the server in the upcoming section).

Create a styles.js file within the utils folder and copy the code below into the file. It contains all the styling for the chat application.

1import { StyleSheet } from "react-native";
2
3export const styles = StyleSheet.create({
4    loginscreen: {
5        flex: 1,
6        backgroundColor: "#EEF1FF",
7        alignItems: "center",
8        justifyContent: "center",
9        padding: 12,
10        width: "100%",
11    },
12    loginheading: {
13        fontSize: 26,
14        marginBottom: 10,
15    },
16    logininputContainer: {
17        width: "100%",
18        alignItems: "center",
19        justifyContent: "center",
20    },
21    logininput: {
22        borderWidth: 1,
23        width: "90%",
24        padding: 8,
25        borderRadius: 2,
26    },
27    loginbutton: {
28        backgroundColor: "green",
29        padding: 12,
30        marginVertical: 10,
31        width: "60%",
32        borderRadius: "50%",
33        elevation: 1,
34    },
35    loginbuttonText: {
36        textAlign: "center",
37        color: "#fff",
38        fontWeight: "600",
39    },
40    chatscreen: {
41        backgroundColor: "#F7F7F7",
42        flex: 1,
43        padding: 10,
44        position: "relative",
45    },
46    chatheading: {
47        fontSize: 24,
48        fontWeight: "bold",
49        color: "green",
50    },
51    chattopContainer: {
52        backgroundColor: "#F7F7F7",
53        height: 70,
54        width: "100%",
55        padding: 20,
56        justifyContent: "center",
57        marginBottom: 15,
58        elevation: 2,
59    },
60    chatheader: {
61        flexDirection: "row",
62        alignItems: "center",
63        justifyContent: "space-between",
64    },
65    chatlistContainer: {
66        paddingHorizontal: 10,
67    },
68    chatemptyContainer: {
69        width: "100%",
70        height: "80%",
71        alignItems: "center",
72        justifyContent: "center",
73    },
74    chatemptyText: { fontWeight: "bold", fontSize: 24, paddingBottom: 30 },
75    messagingscreen: {
76        flex: 1,
77    },
78    messaginginputContainer: {
79        width: "100%",
80        minHeight: 100,
81        backgroundColor: "white",
82        paddingVertical: 30,
83        paddingHorizontal: 15,
84        justifyContent: "center",
85        flexDirection: "row",
86    },
87    messaginginput: {
88        borderWidth: 1,
89        padding: 15,
90        flex: 1,
91        marginRight: 10,
92        borderRadius: 20,
93    },
94    messagingbuttonContainer: {
95        width: "30%",
96        backgroundColor: "green",
97        borderRadius: 3,
98        alignItems: "center",
99        justifyContent: "center",
100        borderRadius: 50,
101    },
102    modalbutton: {
103        width: "40%",
104        height: 45,
105        backgroundColor: "green",
106        borderRadius: 5,
107        alignItems: "center",
108        justifyContent: "center",
109        color: "#fff",
110    },
111    modalbuttonContainer: {
112        flexDirection: "row",
113        justifyContent: "space-between",
114        marginTop: 10,
115    },
116    modaltext: {
117        color: "#fff",
118    },
119    modalContainer: {
120        width: "100%",
121        borderTopColor: "#ddd",
122        borderTopWidth: 1,
123        elevation: 1,
124        height: 400,
125        backgroundColor: "#fff",
126        position: "absolute",
127        bottom: 0,
128        zIndex: 10,
129        paddingVertical: 50,
130        paddingHorizontal: 20,
131    },
132    modalinput: {
133        borderWidth: 2,
134        padding: 15,
135    },
136    modalsubheading: {
137        fontSize: 20,
138        fontWeight: "bold",
139        marginBottom: 15,
140        textAlign: "center",
141    },
142    mmessageWrapper: {
143        width: "100%",
144        alignItems: "flex-start",
145        marginBottom: 15,
146    },
147    mmessage: {
148        maxWidth: "50%",
149        backgroundColor: "#f5ccc2",
150        padding: 15,
151        borderRadius: 10,
152        marginBottom: 2,
153    },
154    mvatar: {
155        marginRight: 5,
156    },
157    cchat: {
158        width: "100%",
159        flexDirection: "row",
160        alignItems: "center",
161        borderRadius: 5,
162        paddingHorizontal: 15,
163        backgroundColor: "#fff",
164        height: 80,
165        marginBottom: 10,
166    },
167    cavatar: {
168        marginRight: 15,
169    },
170    cusername: {
171        fontSize: 18,
172        marginBottom: 5,
173        fontWeight: "bold",
174    },
175    cmessage: {
176        fontSize: 14,
177        opacity: 0.7,
178    },
179    crightContainer: {
180        flexDirection: "row",
181        justifyContent: "space-between",
182        flex: 1,
183    },
184    ctime: {
185        opacity: 0.5,
186    },
187});

Install React Navigation and its dependencies. React Navigation allows us to navigate from one screen to another within a React Native application.

1npm install @react-navigation/native
2npx expo install react-native-screens react-native-safe-area-context

Setting up the Socket.io Node.js server

Here, I will guide you through creating the Socket.io Node.js server for real-time communication with the React Native application.

Create a server folder within the project folder.

1cd chat-app
2mkdir server

Navigate into the server folder and create a package.json file.

1cd server & npm init -y

Install Express.js, CORS, Nodemon, and Socket.io Server API.

1npm install express cors nodemon socket.io

Express.js 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.

1touch 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.

1//👇🏻 index.js
2const express = require("express");
3const app = express();
4const PORT = 4000;
5
6app.use(express.urlencoded({ extended: true }));
7app.use(express.json());
8
9app.get("/api", (req, res) => {
10    res.json({
11        message: "Hello world",
12    });
13});
14
15app.listen(PORT, () => {
16    console.log(`Server listening on ${PORT}`);
17});

Import the HTTP and the CORS library to allow data transfer between the client and the server domains.

1const express = require("express");
2const app = express();
3const PORT = 4000;
4
5app.use(express.urlencoded({ extended: true }));
6app.use(express.json());
7
8//👇🏻 New imports
9const http = require("http").Server(app);
10const cors = require("cors");
11
12app.use(cors());
13
14app.get("/api", (req, res) => {
15    res.json({
16        message: "Hello world",
17    });
18});
19
20http.listen(PORT, () => {
21    console.log(`Server listening on ${PORT}`);
22});

Next, add Socket.io to the project to create a real-time connection. Before the app.get() block, copy the code below:

1//👇🏻 New imports
2.....
3const socketIO = require('socket.io')(http, {
4    cors: {
5        origin: "<http://localhost:3000>"
6    }
7});
8
9//👇🏻 Add this before the app.get() block
10socketIO.on('connection', (socket) => {
11    console.log(`⚡: ${socket.id} user just connected!`);
12
13    socket.on('disconnect', () => {
14      socket.disconnect()
15      console.log('🔥: A user disconnected');
16    });
17});
18

From the code snippet above, the socket.io("connection") function establishes a connection with the React app, then 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.

1//👇🏻 In server/package.json
2
3"scripts": {
4    "test": "echo \\"Error: no test specified\\" && exit 1",
5    "start": "nodemon index.js"
6  },

You can now run the server with Nodemon by using the command below.

1npm start

Building the user interface

Here, we’ll create the user interface for the chat application to enable users to sign in, create chat rooms, and send messages. The app is divided into three screens – the Login screen, the Chat screen, and the Messaging screen.

interface

First, let’s set up React Navigation.

Create a screens folder within the app folder, add the Login, Chat, and Messaging components and render a "Hello World" text within them.

1mkdir screens
2touch Login.js Chat.js Messaging.js

Copy the code below into the App.js file within the app folder.

1import React from "react";
2
3//👇🏻 app screens
4import Login from "./screens/Login";
5import Messaging from "./screens/Messaging";
6import Chat from "./screens/Chat";
7
8//👇🏻 React Navigation configurations
9import { NavigationContainer } from "@react-navigation/native";
10import { createNativeStackNavigator } from "@react-navigation/native-stack";
11
12const Stack = createNativeStackNavigator();
13
14export default function App() {
15    return (
16        <NavigationContainer>
17            <Stack.Navigator>
18                <Stack.Screen
19                    name='Login'
20                    component={Login}
21                    options={{ headerShown: false }}
22                />
23
24                <Stack.Screen
25                    name='Chat'
26                    component={Chat}
27                    options={{
28                        title: "Chats",
29                        headerShown: false,
30                    }}
31                />
32                <Stack.Screen name='Messaging' component={Messaging} />
33            </Stack.Navigator>
34        </NavigationContainer>
35    );
36}

The Login screen

Copy the code below into the Login.js file.

1import React, { useState } from "react";
2import {
3    Text,
4    SafeAreaView,
5    View,
6    TextInput,
7    Pressable,
8    Alert,
9} from "react-native";
10
11//👇🏻 Import the app styles
12import { styles } from "../utils/styles";
13
14const Login = ({ navigation }) => {
15    const [username, setUsername] = useState("");
16
17    //👇🏻 checks if the input field is empty
18    const handleSignIn = () => {
19        if (username.trim()) {
20            //👇🏻 Logs the username to the console
21            console.log({ username });
22        } else {
23            Alert.alert("Username is required.");
24        }
25    };
26
27    return (
28        <SafeAreaView style={styles.loginscreen}>
29            <View style={styles.loginscreen}>
30                <Text style={styles.loginheading}>Sign in</Text>
31                <View style={styles.logininputContainer}>
32                    <TextInput
33                        autoCorrect={false}
34                        placeholder='Enter your username'
35                        style={styles.logininput}
36                        onChangeText={(value) => setUsername(value)}
37                    />
38                </View>
39
40                <Pressable onPress={handleSignIn} style={styles.loginbutton}>
41                    <View>
42                        <Text style={styles.loginbuttonText}>Get Started</Text>
43                    </View>
44                </Pressable>
45            </View>
46        </SafeAreaView>
47    );
48};
49
50export default Login;

The code snippet accepts the username from the user and logs it on the console.

Let’s update the code and save the username using Async Storage, so users will not be required to sign in to the application every time they launch the app.

💡 *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 various data in string format.*

Run the code below to install Async Storage

1expo install @react-native-async-storage/async-storage

Update the handleSignIn function to save the username via AsyncStorage.

1import AsyncStorage from "@react-native-async-storage/async-storage";
2
3const storeUsername = async () => {
4        try {
5            //👇🏻 async function - saves the username to AsyncStorage
6            //   redirecting to the Chat page
7            await AsyncStorage.setItem("username", username);
8            navigation.navigate("Chat");
9        } catch (e) {
10            Alert.alert("Error! While saving username");
11        }
12    };
13
14    const handleSignIn = () => {
15        if (username.trim()) {
16            //👇🏻 calls AsyncStorage function
17            storeUsername();
18        } else {
19            Alert.alert("Username is required.");
20        }
21    };

The Chat room

Here, we’ll update the user interface for the Chat screen to display the available chat rooms, allow users to create one, and navigate to the Messaging screen when each room is selected.GIF

ChatRoom

Copy the code below into the Chat.js file.

1import React from "react";
2import { View, Text, Pressable, SafeAreaView, FlatList } from "react-native";
3import { Feather } from "@expo/vector-icons";
4
5import ChatComponent from "../component/ChatComponent";
6import { styles } from "../utils/styles";
7
8const Chat = () => {
9
10    //👇🏻 Dummy list of rooms
11    const rooms = [
12        {
13            id: "1",
14            name: "Novu Hangouts",
15            messages: [
16                {
17                    id: "1a",
18                    text: "Hello guys, welcome!",
19                    time: "07:50",
20                    user: "Tomer",
21                },
22                {
23                    id: "1b",
24                    text: "Hi Tomer, thank you! 😇",
25                    time: "08:50",
26                    user: "David",
27                },
28            ],
29        },
30        {
31            id: "2",
32            name: "Hacksquad Team 1",
33            messages: [
34                {
35                    id: "2a",
36                    text: "Guys, who's awake? 🙏🏽",
37                    time: "12:50",
38                    user: "Team Leader",
39                },
40                {
41                    id: "2b",
42                    text: "What's up? 🧑🏻‍💻",
43                    time: "03:50",
44                    user: "Victoria",
45                },
46            ],
47        },
48    ];
49
50    return (
51        <SafeAreaView style={styles.chatscreen}>
52            <View style={styles.chattopContainer}>
53                <View style={styles.chatheader}>
54                    <Text style={styles.chatheading}>Chats</Text>
55
56            {/* 👇🏻 Logs "ButtonPressed" to the console when the icon is clicked */}
57                    <Pressable onPress={() => console.log("Button Pressed!")}>
58                        <Feather name='edit' size={24} color='green' />
59                    </Pressable>
60                </View>
61            </View>
62
63            <View style={styles.chatlistContainer}>
64                {rooms.length > 0 ? (
65                    <FlatList
66                        data={rooms}
67                        renderItem={({ item }) => <ChatComponent item={item} />}
68                        keyExtractor={(item) => item.id}
69                    />
70                ) : (
71                    <View style={styles.chatemptyContainer}>
72                        <Text style={styles.chatemptyText}>No rooms created!</Text>
73                        <Text>Click the icon above to create a Chat room</Text>
74                    </View>
75                )}
76            </View>
77        </SafeAreaView>
78    );
79};
80
81export default Chat;
  • From the code snippet above:
    • I created a dummy list of rooms, then rendered them through a FlatList into the ChatComponent.(yet to be created)
    • Since the rooms can either be empty or populated, the conditional statement determines the component to display.
Code Snippet

Next, create the ChatComponent within a component folder. It represents a preview of each chat name, time, the last message sent and redirects users to the Messaging component when clicked.

Copy the code below into the components/ChatComponent.js file.

1import { View, Text, Pressable } from "react-native";
2import React, { useLayoutEffect, useState } from "react";
3import { Ionicons } from "@expo/vector-icons";
4import { useNavigation } from "@react-navigation/native";
5import { styles } from "../utils/styles";
6
7const ChatComponent = ({ item }) => {
8    const navigation = useNavigation();
9    const [messages, setMessages] = useState({});
10
11    //👇🏻 Retrieves the last message in the array from the item prop
12    useLayoutEffect(() => {
13        setMessages(item.messages[item.messages.length - 1]);
14    }, []);
15
16    ///👇🏻 Navigates to the Messaging screen
17    const handleNavigation = () => {
18        navigation.navigate("Messaging", {
19            id: item.id,
20            name: item.name,
21        });
22    };
23
24    return (
25        <Pressable style={styles.cchat} onPress={handleNavigation}>
26            <Ionicons
27                name='person-circle-outline'
28                size={45}
29                color='black'
30                style={styles.cavatar}
31            />
32
33            <View style={styles.crightContainer}>
34                <View>
35                    <Text style={styles.cusername}>{item.name}</Text>
36
37                    <Text style={styles.cmessage}>
38                        {messages?.text ? messages.text : "Tap to start chatting"}
39                    </Text>
40                </View>
41                <View>
42                    <Text style={styles.ctime}>
43                        {messages?.time ? messages.time : "now"}
44                    </Text>
45                </View>
46            </View>
47        </Pressable>
48    );
49};
50
51export default ChatComponent;

Congratulations💃🏻! We can now display the list of rooms and redirect the user the Messaging screen.

Before we proceed, let’s create a custom Modal component that allows users to create a new group (room) when we press the header icon.

Before

Create a Modal.js file within the components folder, import it into the Chat screen and toggle it whenever we press the header icon.

1import React from "react";
2import { View, Text, Pressable, SafeAreaView, FlatList } from "react-native";
3import { Feather } from "@expo/vector-icons";
4import ChatComponent from "../component/ChatComponent";
5import { styles } from "../utils/styles";
6
7//👇🏻 The Modal component
8import Modal from "../component/Modal";
9
10const Chat = () => {
11    const [visible, setVisible] = useState(false);
12
13    //...other variables
14    return (
15        <SafeAreaView style={styles.chatscreen}>
16            <View style={styles.chattopContainer}>
17                <View style={styles.chatheader}>
18                    <Text style={styles.chatheading}>Chats</Text>
19
20                    {/* Displays the Modal component when clicked */}
21                    <Pressable onPress={() => setVisible(true)}>
22                        <Feather name='edit' size={24} color='green' />
23                    </Pressable>
24                </View>
25            </View>
26
27            <View style={styles.chatlistContainer}>...</View>
28            {/*
29                Pass setVisible as prop in order to toggle 
30                the display within the Modal component.
31            */}
32            {visible ? <Modal setVisible={setVisible} /> : ""}
33        </SafeAreaView>
34    );
35};
36
37export default Chat;

Copy the code below into the Modal.js file.

1import { View, Text, TextInput, Pressable } from "react-native";
2import React, { useState } from "react";
3import { styles } from "../utils/styles";
4
5const Modal = ({ setVisible }) => {
6    const [groupName, setGroupName] = useState("");
7
8    //👇🏻 Function that closes the Modal component
9    const closeModal = () => setVisible(false);
10
11    //👇🏻 Logs the group name to the console
12    const handleCreateRoom = () => {
13        console.log({ groupName });
14        closeModal();
15    };
16    return (
17        <View style={styles.modalContainer}>
18            <Text style={styles.modalsubheading}>Enter your Group name</Text>
19            <TextInput
20                style={styles.modalinput}
21                placeholder='Group name'
22                onChangeText={(value) => setGroupName(value)}
23            />
24
25            <View style={styles.modalbuttonContainer}>
26                <Pressable style={styles.modalbutton} onPress={handleCreateRoom}>
27                    <Text style={styles.modaltext}>CREATE</Text>
28                </Pressable>
29                <Pressable
30                    style={[styles.modalbutton, { backgroundColor: "#E14D2A" }]}
31                    onPress={closeModal}
32                >
33                    <Text style={styles.modaltext}>CANCEL</Text>
34                </Pressable>
35            </View>
36        </View>
37    );
38};
39
40export default Modal;

The Messaging screen

Copy the code below into the Messaging.js file.

1import React, { useLayoutEffect, useState } from "react";
2import { View, TextInput, Text, FlatList, Pressable } from "react-native";
3import AsyncStorage from "@react-native-async-storage/async-storage";
4import MessageComponent from "../component/MessageComponent";
5import { styles } from "../utils/styles";
6
7const Messaging = ({ route, navigation }) => {
8    const [chatMessages, setChatMessages] = useState([
9        {
10            id: "1",
11            text: "Hello guys, welcome!",
12            time: "07:50",
13            user: "Tomer",
14        },
15        {
16            id: "2",
17            text: "Hi Tomer, thank you! 😇",
18            time: "08:50",
19            user: "David",
20        },
21    ]);
22    const [message, setMessage] = useState("");
23    const [user, setUser] = useState("");
24
25    //👇🏻 Access the chatroom's name and id
26    const { name, id } = route.params;
27
28//👇🏻 This function gets the username saved on AsyncStorage
29    const getUsername = async () => {
30        try {
31            const value = await AsyncStorage.getItem("username");
32            if (value !== null) {
33                setUser(value);
34            }
35        } catch (e) {
36            console.error("Error while loading username!");
37        }
38    };
39
40    //👇🏻 Sets the header title to the name chatroom's name
41    useLayoutEffect(() => {
42        navigation.setOptions({ title: name });
43        getUsername()
44    }, []);
45
46    /*👇🏻 
47        This function gets the time the user sends a message, then 
48        logs the username, message, and the timestamp to the console.
49     */
50    const handleNewMessage = () => {
51        const hour =
52            new Date().getHours() < 10
53                ? `0${new Date().getHours()}`
54                : `${new Date().getHours()}`;
55
56        const mins =
57            new Date().getMinutes() < 10
58                ? `0${new Date().getMinutes()}`
59                : `${new Date().getMinutes()}`;
60
61        console.log({
62            message,
63            user,
64            timestamp: { hour, mins },
65        });
66    };
67
68    return (
69        <View style={styles.messagingscreen}>
70            <View
71                style={[
72                    styles.messagingscreen,
73                    { paddingVertical: 15, paddingHorizontal: 10 },
74                ]}
75            >
76                {chatMessages[0] ? (
77                    <FlatList
78                        data={chatMessages}
79                        renderItem={({ item }) => (
80                            <MessageComponent item={item} user={user} />
81                        )}
82                        keyExtractor={(item) => item.id}
83                    />
84                ) : (
85                    ""
86                )}
87            </View>
88
89            <View style={styles.messaginginputContainer}>
90                <TextInput
91                    style={styles.messaginginput}
92                    onChangeText={(value) => setMessage(value)}
93                />
94                <Pressable
95                    style={styles.messagingbuttonContainer}
96                    onPress={handleNewMessage}
97                >
98                    <View>
99                        <Text style={{ color: "#f2f0f1", fontSize: 20 }}>SEND</Text>
100                    </View>
101                </Pressable>
102            </View>
103        </View>
104    );
105};
106
107export default Messaging;

The code snippet above renders the messages in each chatroom via the MessageComponent component.

Create the MessageComponent file and copy the code below into the file:

1import { View, Text } from "react-native";
2import React from "react";
3import { Ionicons } from "@expo/vector-icons";
4import { styles } from "../utils/styles";
5
6export default function MessageComponent({ item, user }) {
7    const status = item.user !== user;
8
9    return (
10        <View>
11            <View
12                style={
13                    status
14                        ? styles.mmessageWrapper
15                        : [styles.mmessageWrapper, { alignItems: "flex-end" }]
16                }
17            >
18                <View style={{ flexDirection: "row", alignItems: "center" }}>
19                    <Ionicons
20                        name='person-circle-outline'
21                        size={30}
22                        color='black'
23                        style={styles.mavatar}
24                    />
25                    <View
26                        style={
27                            status
28                                ? styles.mmessage
29                                : [styles.mmessage, { backgroundColor: "rgb(194, 243, 194)" }]
30                        }
31                    >
32                        <Text>{item.text}</Text>
33                    </View>
34                </View>
35                <Text style={{ marginLeft: 40 }}>{item.time}</Text>
36            </View>
37        </View>
38    );
39}

From the code snippet above, the status variable checks if the user key on the message is the same as the current user so as to determine how to align the messages.

Image description

We’ve now completed the user interface for the application!🎊 Next, let’s learn how to communicate with the Socket.io server, create chat rooms, and send messages in real time via Socket.io.

Creating chat rooms with Socket.io in React Native

In this section, I’ll guide you through creating chat rooms on the Socket.io server and displaying them on the app.

Update the Modal.js file to send a message to the server when we create a new chat room.

1import { View, Text, TextInput, Pressable } from "react-native";
2import React, { useState } from "react";
3import { styles } from "../utils/styles";
4
5//👇🏻 Import socket from the socket.js file in utils folder
6import socket from "../utils/socket";
7
8const Modal = ({ setVisible }) => {
9    const closeModal = () => setVisible(false);
10    const [groupName, setGroupName] = useState("");
11
12    const handleCreateRoom = () => {
13        //👇🏻 sends a message containing the group name to the server
14        socket.emit("createRoom", groupName);
15        closeModal();
16    };
17    return (
18        <View style={styles.modalContainer}>
19            <Text style={styles.modalsubheading}>Enter your Group name</Text>
20            <TextInput
21                style={styles.modalinput}
22                placeholder='Group name'
23                onChangeText={(value) => setGroupName(value)}
24            />
25            <View style={styles.modalbuttonContainer}>
26                {/* 👇🏻 The create button triggers the function*/}
27                <Pressable style={styles.modalbutton} onPress={handleCreateRoom}>
28                    <Text style={styles.modaltext}>CREATE</Text>
29                </Pressable>
30
31                <Pressable
32                    style={[styles.modalbutton, { backgroundColor: "#E14D2A" }]}
33                    onPress={closeModal}
34                >
35                    <Text style={styles.modaltext}>CANCEL</Text>
36                </Pressable>
37            </View>
38        </View>
39    );
40};
41
42export default Modal;

Create a listener on the backend server that saves the group name to an array and returns the whole list.

1//👇🏻 Generates random string as the ID
2const generateID = () => Math.random().toString(36).substring(2, 10);
3
4let chatRooms = [
5    //👇🏻 Here is the data structure of each chatroom
6    // {
7    //  id: generateID(),
8    //  name: "Novu Hangouts",
9    //  messages: [
10    //      {
11    //          id: generateID(),
12    //          text: "Hello guys, welcome!",
13    //          time: "07:50",
14    //          user: "Tomer",
15    //      },
16    //      {
17    //          id: generateID(),
18    //          text: "Hi Tomer, thank you! 😇",
19    //          time: "08:50",
20    //          user: "David",
21    //      },
22    //  ],
23    // },
24];
25
26socketIO.on("connection", (socket) => {
27    console.log(`⚡: ${socket.id} user just connected!`);
28
29    socket.on("createRoom", (roomName) => {
30        socket.join(roomName);
31        //👇🏻 Adds the new group name to the chat rooms array
32        chatRooms.unshift({ id: generateID(), roomName, messages: [] });
33        //👇🏻 Returns the updated chat rooms via another event
34        socket.emit("roomsList", chatRooms);
35    });
36
37    socket.on("disconnect", () => {
38        socket.disconnect();
39        console.log("🔥: A user disconnected");
40    });
41});

Also, return the chat room list via the API route as below:

1app.get("/api", (req, res) => {
2    res.json(chatRooms);
3});

Update the Chat.js file to fetch and listen to the roomsList from the server and display the chat rooms.

1const [rooms, setRooms] = useState([]);
2
3//👇🏻 Runs when the component mounts
4useLayoutEffect(() => {
5    function fetchGroups() {
6        fetch("http://localhost:4000/api")
7            .then((res) => res.json())
8            .then((data) => setRooms(data))
9            .catch((err) => console.error(err));
10    }
11    fetchGroups();
12}, []);
13
14//👇🏻 Runs whenever there is new trigger from the backend
15useEffect(() => {
16    socket.on("roomsList", (rooms) => {
17        setRooms(rooms);
18    });
19}, [socket]);

Sending messages via Socket.io in React Native

In the previous section, we were able to create new chat rooms, save them in an array on the server, and display them within the app. Here, we’ll update the chat room messages by adding new messages to the sub-array.

How to display the chat room messages

Recall that the id of each chat room is passed into the Messaging component. Now, let’s send the id to the server via Socket.io when the screen loads.

1import React, { useEffect, useLayoutEffect, useState } from "react";
2import { View, TextInput, Text, FlatList, Pressable } from "react-native";
3import socket from "../utils/socket";
4import MessageComponent from "../component/MessageComponent";
5import { styles } from "../utils/styles";
6
7const Messaging = ({ route, navigation }) => {
8    //👇🏻 The id passed
9    const { name, id } = route.params;
10
11//...other functions
12
13    useLayoutEffect(() => {
14        navigation.setOptions({ title: name });
15
16        //👇🏻 Sends the id to the server to fetch all its messages
17        socket.emit("findRoom", id);
18    }, []);
19
20    return <View style={styles.messagingscreen}>...</View>;
21};
22
23export default Messaging;

Create the event listener on the server.

1socket.on("findRoom", (id) => {
2    //👇🏻 Filters the array by the ID
3    let result = chatRooms.filter((room) => room.id == id);
4    //👇🏻 Sends the messages to the app
5    socket.emit("foundRoom", result[0].messages);
6});

Next, listen to the foundRoom event and display the messages to the user.

1//👇🏻 This runs only initial mount
2useLayoutEffect(() => {
3    navigation.setOptions({ title: name });
4    socket.emit("findRoom", id);
5    socket.on("foundRoom", (roomChats) => setChatMessages(roomChats));
6}, []);
7
8//👇🏻 This runs when the messages are updated.
9useEffect(() => {
10    socket.on("foundRoom", (roomChats) => setChatMessages(roomChats));
11}, [socket])

How to create new messages

To create new messages, we need to update the handleNewMessage function to send message property to the server and add it to the messages array.

1const handleNewMessage = () => {
2    const hour =
3        new Date().getHours() < 10
4            ? `0${new Date().getHours()}`
5            : `${new Date().getHours()}`;
6
7    const mins =
8        new Date().getMinutes() < 10
9            ? `0${new Date().getMinutes()}`
10            : `${new Date().getMinutes()}`;
11
12    socket.emit("newMessage", {
13        message,
14        room_id: id,
15        user,
16        timestamp: { hour, mins },
17    });
18};

Listen to the event on the server and update the chatRoom array.

1socket.on("newMessage", (data) => {
2    //👇🏻 Destructures the property from the object
3    const { room_id, message, user, timestamp } = data;
4
5    //👇🏻 Finds the room where the message was sent
6    let result = chatRooms.filter((room) => room.id == room_id);
7
8    //👇🏻 Create the data structure for the message
9    const newMessage = {
10        id: generateID(),
11        text: message,
12        user,
13        time: `${timestamp.hour}:${timestamp.mins}`,
14    };
15    //👇🏻 Updates the chatroom messages
16    socket.to(result[0].name).emit("roomMessage", newMessage);
17    result[0].messages.push(newMessage);
18
19    //👇🏻 Trigger the events to reflect the new changes
20    socket.emit("roomsList", chatRooms);
21    socket.emit("foundRoom", result[0].messages);
22});

Conclusion

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.

Socket.io is a great tool with excellent features that enables us to build efficient real-time applications like sports betting websites, auction and forex trading applications, and of course, chat applications by creating lasting connections with a Node.js server.

Feel free to improve the application by:

  • adding authentication
  • saving the token with Async Storage
  • adding a real-time database for the messages, and
  • adding push notifications with the expo notification package.

The source code for this tutorial is available here:

https://github.com/novuhq/blog/tree/main/chat-app-reactnative-socketIO

Thank you for reading!💃🏻

P.S Novu is sending awesome swag on Hacktoberfest! Come and participate! Happy if you can support us by giving us a star! ⭐️
https://github.com/novuhq/novu

Nevo David
Nevo DavidOctober 17, 2022

Related Posts

How to

Building a beautiful Kanban board with Node.js, React, and Websockets 🦄 ✨

In this article, you'll learn how to build a Kanban Board the same as you have in JIRA, Monday and Trello. We will do it with a beautiful drag-and-drop feature using React, Socket.io, and React beautiful DND. Users will be able to sign in, create and update various tasks, and add comments.

Nevo David
Nevo DavidSeptember 28, 2022
How to

Building an interactive screen-sharing app with Puppeteer and React 🤯

Give the user the ability to browse a webpage through your system and feel like it's a real browser.

Nevo David
Nevo DavidSeptember 12, 2022
How to

Building a live-event alert system with React, Socket.io, and Push Notifications 🚀

You want everybody to get a message on your website once something happens in real-time. That can be a new version, announcement, or some product updates.

Nevo David
Nevo DavidSeptember 5, 2022