DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for Building a chat app with Socket.io and React Native 🀯
Nevo David for novu

Posted on • Originally published at novu.co

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

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.

What is this article about?

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.

Typing

Chats are fun, you send a message to a person or a group, they see the message and reply back. 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 people in the group.

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 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 chat-app
cd chat-app
expo init app
Enter fullscreen mode Exit fullscreen mode

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.

? Choose a template: β€Ί - Use arrow-keys. Return to submit.
    ----- Managed workflow -----
❯   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
Enter fullscreen mode Exit fullscreen mode

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

cd app
expo install socket.io-client
Enter fullscreen mode Exit fullscreen mode

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

mkdir utils
touch socket.js
//πŸ‘‡πŸ» Paste within socket.js file
Enter fullscreen mode Exit fullscreen mode

And add

import { io } from "socket.io-client";
const socket = io.connect("http://localhost:4000");
export default socket;
Enter fullscreen mode Exit fullscreen mode

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.

import { StyleSheet } from "react-native";

export const styles = StyleSheet.create({
    loginscreen: {
        flex: 1,
        backgroundColor: "#EEF1FF",
        alignItems: "center",
        justifyContent: "center",
        padding: 12,
        width: "100%",
    },
    loginheading: {
        fontSize: 26,
        marginBottom: 10,
    },
    logininputContainer: {
        width: "100%",
        alignItems: "center",
        justifyContent: "center",
    },
    logininput: {
        borderWidth: 1,
        width: "90%",
        padding: 8,
        borderRadius: 2,
    },
    loginbutton: {
        backgroundColor: "green",
        padding: 12,
        marginVertical: 10,
        width: "60%",
        borderRadius: "50%",
        elevation: 1,
    },
    loginbuttonText: {
        textAlign: "center",
        color: "#fff",
        fontWeight: "600",
    },
    chatscreen: {
        backgroundColor: "#F7F7F7",
        flex: 1,
        padding: 10,
        position: "relative",
    },
    chatheading: {
        fontSize: 24,
        fontWeight: "bold",
        color: "green",
    },
    chattopContainer: {
        backgroundColor: "#F7F7F7",
        height: 70,
        width: "100%",
        padding: 20,
        justifyContent: "center",
        marginBottom: 15,
        elevation: 2,
    },
    chatheader: {
        flexDirection: "row",
        alignItems: "center",
        justifyContent: "space-between",
    },
    chatlistContainer: {
        paddingHorizontal: 10,
    },
    chatemptyContainer: {
        width: "100%",
        height: "80%",
        alignItems: "center",
        justifyContent: "center",
    },
    chatemptyText: { fontWeight: "bold", fontSize: 24, paddingBottom: 30 },
    messagingscreen: {
        flex: 1,
    },
    messaginginputContainer: {
        width: "100%",
        minHeight: 100,
        backgroundColor: "white",
        paddingVertical: 30,
        paddingHorizontal: 15,
        justifyContent: "center",
        flexDirection: "row",
    },
    messaginginput: {
        borderWidth: 1,
        padding: 15,
        flex: 1,
        marginRight: 10,
        borderRadius: 20,
    },
    messagingbuttonContainer: {
        width: "30%",
        backgroundColor: "green",
        borderRadius: 3,
        alignItems: "center",
        justifyContent: "center",
        borderRadius: 50,
    },
    modalbutton: {
        width: "40%",
        height: 45,
        backgroundColor: "green",
        borderRadius: 5,
        alignItems: "center",
        justifyContent: "center",
        color: "#fff",
    },
    modalbuttonContainer: {
        flexDirection: "row",
        justifyContent: "space-between",
        marginTop: 10,
    },
    modaltext: {
        color: "#fff",
    },
    modalContainer: {
        width: "100%",
        borderTopColor: "#ddd",
        borderTopWidth: 1,
        elevation: 1,
        height: 400,
        backgroundColor: "#fff",
        position: "absolute",
        bottom: 0,
        zIndex: 10,
        paddingVertical: 50,
        paddingHorizontal: 20,
    },
    modalinput: {
        borderWidth: 2,
        padding: 15,
    },
    modalsubheading: {
        fontSize: 20,
        fontWeight: "bold",
        marginBottom: 15,
        textAlign: "center",
    },
    mmessageWrapper: {
        width: "100%",
        alignItems: "flex-start",
        marginBottom: 15,
    },
    mmessage: {
        maxWidth: "50%",
        backgroundColor: "#f5ccc2",
        padding: 15,
        borderRadius: 10,
        marginBottom: 2,
    },
    mvatar: {
        marginRight: 5,
    },
    cchat: {
        width: "100%",
        flexDirection: "row",
        alignItems: "center",
        borderRadius: 5,
        paddingHorizontal: 15,
        backgroundColor: "#fff",
        height: 80,
        marginBottom: 10,
    },
    cavatar: {
        marginRight: 15,
    },
    cusername: {
        fontSize: 18,
        marginBottom: 5,
        fontWeight: "bold",
    },
    cmessage: {
        fontSize: 14,
        opacity: 0.7,
    },
    crightContainer: {
        flexDirection: "row",
        justifyContent: "space-between",
        flex: 1,
    },
    ctime: {
        opacity: 0.5,
    },
});
Enter fullscreen mode Exit fullscreen mode

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

npm install @react-navigation/native
npx expo install react-native-screens react-native-safe-area-context
Enter fullscreen mode Exit fullscreen mode

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.

cd chat-app
mkdir server
Enter fullscreen mode Exit fullscreen mode

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

cd server & npm init -y
Enter fullscreen mode Exit fullscreen mode

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

npm install express cors nodemon socket.io
Enter fullscreen mode Exit fullscreen mode

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.

touch index.js
Enter fullscreen mode Exit fullscreen mode

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.

//πŸ‘‡πŸ» index.js
const express = require("express");
const app = express();
const PORT = 4000;

app.use(express.urlencoded({ extended: true }));
app.use(express.json());

app.get("/api", (req, res) => {
    res.json({
        message: "Hello world",
    });
});

app.listen(PORT, () => {
    console.log(`Server listening on ${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

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

const express = require("express");
const app = express();
const PORT = 4000;

app.use(express.urlencoded({ extended: true }));
app.use(express.json());

//πŸ‘‡πŸ» New imports
const http = require("http").Server(app);
const cors = require("cors");

app.use(cors());

app.get("/api", (req, res) => {
    res.json({
        message: "Hello world",
    });
});

http.listen(PORT, () => {
    console.log(`Server listening on ${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

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

//πŸ‘‡πŸ» New imports
.....
const socketIO = require('socket.io')(http, {
    cors: {
        origin: "<http://localhost:3000>"
    }
});

//πŸ‘‡πŸ» Add this before the app.get() block
socketIO.on('connection', (socket) => {
    console.log(`⚑: ${socket.id} user just connected!`);

    socket.on('disconnect', () => {
      socket.disconnect()
      console.log('πŸ”₯: A user disconnected');
    });
});

Enter fullscreen mode Exit fullscreen mode

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.

//πŸ‘‡πŸ» In server/package.json

"scripts": {
    "test": "echo \\"Error: no test specified\\" && exit 1",
    "start": "nodemon index.js"
  },

Enter fullscreen mode Exit fullscreen mode

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

npm start
Enter fullscreen mode Exit fullscreen mode

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.

mkdir screens
touch Login.js Chat.js Messaging.js
Enter fullscreen mode Exit fullscreen mode

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

import React from "react";

//πŸ‘‡πŸ» app screens
import Login from "./screens/Login";
import Messaging from "./screens/Messaging";
import Chat from "./screens/Chat";

//πŸ‘‡πŸ» React Navigation configurations
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";

const Stack = createNativeStackNavigator();

export default function App() {
    return (
        <NavigationContainer>
            <Stack.Navigator>
                <Stack.Screen
                    name='Login'
                    component={Login}
                    options={{ headerShown: false }}
                />

                <Stack.Screen
                    name='Chat'
                    component={Chat}
                    options={{
                        title: "Chats",
                        headerShown: false,
                    }}
                />
                <Stack.Screen name='Messaging' component={Messaging} />
            </Stack.Navigator>
        </NavigationContainer>
    );
}
Enter fullscreen mode Exit fullscreen mode

The Login screen

Copy the code below into the Login.js file.

import React, { useState } from "react";
import {
    Text,
    SafeAreaView,
    View,
    TextInput,
    Pressable,
    Alert,
} from "react-native";

//πŸ‘‡πŸ» Import the app styles
import { styles } from "../utils/styles";

const Login = ({ navigation }) => {
    const [username, setUsername] = useState("");

    //πŸ‘‡πŸ» checks if the input field is empty
    const handleSignIn = () => {
        if (username.trim()) {
            //πŸ‘‡πŸ» Logs the username to the console
            console.log({ username });
        } else {
            Alert.alert("Username is required.");
        }
    };

    return (
        <SafeAreaView style={styles.loginscreen}>
            <View style={styles.loginscreen}>
                <Text style={styles.loginheading}>Sign in</Text>
                <View style={styles.logininputContainer}>
                    <TextInput
                        autoCorrect={false}
                        placeholder='Enter your username'
                        style={styles.logininput}
                        onChangeText={(value) => setUsername(value)}
                    />
                </View>

                <Pressable onPress={handleSignIn} style={styles.loginbutton}>
                    <View>
                        <Text style={styles.loginbuttonText}>Get Started</Text>
                    </View>
                </Pressable>
            </View>
        </SafeAreaView>
    );
};

export default Login;
Enter fullscreen mode Exit fullscreen mode

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

expo install @react-native-async-storage/async-storage
Enter fullscreen mode Exit fullscreen mode

Update the handleSignIn function to save the username via AsyncStorage.

import AsyncStorage from "@react-native-async-storage/async-storage";

const storeUsername = async () => {
        try {
            //πŸ‘‡πŸ» async function - saves the username to AsyncStorage
            //   redirecting to the Chat page
            await AsyncStorage.setItem("username", username);
            navigation.navigate("Chat");
        } catch (e) {
            Alert.alert("Error! While saving username");
        }
    };

    const handleSignIn = () => {
        if (username.trim()) {
            //πŸ‘‡πŸ» calls AsyncStorage function
            storeUsername();
        } else {
            Alert.alert("Username is required.");
        }
    };
Enter fullscreen mode Exit fullscreen mode

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.

ChatRoom

Copy the code below into the Chat.js file.

import React from "react";
import { View, Text, Pressable, SafeAreaView, FlatList } from "react-native";
import { Feather } from "@expo/vector-icons";

import ChatComponent from "../component/ChatComponent";
import { styles } from "../utils/styles";

const Chat = () => {

    //πŸ‘‡πŸ» Dummy list of rooms
    const rooms = [
        {
            id: "1",
            name: "Novu Hangouts",
            messages: [
                {
                    id: "1a",
                    text: "Hello guys, welcome!",
                    time: "07:50",
                    user: "Tomer",
                },
                {
                    id: "1b",
                    text: "Hi Tomer, thank you! πŸ˜‡",
                    time: "08:50",
                    user: "David",
                },
            ],
        },
        {
            id: "2",
            name: "Hacksquad Team 1",
            messages: [
                {
                    id: "2a",
                    text: "Guys, who's awake? πŸ™πŸ½",
                    time: "12:50",
                    user: "Team Leader",
                },
                {
                    id: "2b",
                    text: "What's up? πŸ§‘πŸ»β€πŸ’»",
                    time: "03:50",
                    user: "Victoria",
                },
            ],
        },
    ];

    return (
        <SafeAreaView style={styles.chatscreen}>
            <View style={styles.chattopContainer}>
                <View style={styles.chatheader}>
                    <Text style={styles.chatheading}>Chats</Text>

            {/* πŸ‘‡πŸ» Logs "ButtonPressed" to the console when the icon is clicked */}
                    <Pressable onPress={() => console.log("Button Pressed!")}>
                        <Feather name='edit' size={24} color='green' />
                    </Pressable>
                </View>
            </View>

            <View style={styles.chatlistContainer}>
                {rooms.length > 0 ? (
                    <FlatList
                        data={rooms}
                        renderItem={({ item }) => <ChatComponent item={item} />}
                        keyExtractor={(item) => item.id}
                    />
                ) : (
                    <View style={styles.chatemptyContainer}>
                        <Text style={styles.chatemptyText}>No rooms created!</Text>
                        <Text>Click the icon above to create a Chat room</Text>
                    </View>
                )}
            </View>
        </SafeAreaView>
    );
};

export default Chat;
Enter fullscreen mode Exit fullscreen mode
  • 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.

import { View, Text, Pressable } from "react-native";
import React, { useLayoutEffect, useState } from "react";
import { Ionicons } from "@expo/vector-icons";
import { useNavigation } from "@react-navigation/native";
import { styles } from "../utils/styles";

const ChatComponent = ({ item }) => {
    const navigation = useNavigation();
    const [messages, setMessages] = useState({});

    //πŸ‘‡πŸ» Retrieves the last message in the array from the item prop
    useLayoutEffect(() => {
        setMessages(item.messages[item.messages.length - 1]);
    }, []);

    ///πŸ‘‡πŸ» Navigates to the Messaging screen
    const handleNavigation = () => {
        navigation.navigate("Messaging", {
            id: item.id,
            name: item.name,
        });
    };

    return (
        <Pressable style={styles.cchat} onPress={handleNavigation}>
            <Ionicons
                name='person-circle-outline'
                size={45}
                color='black'
                style={styles.cavatar}
            />

            <View style={styles.crightContainer}>
                <View>
                    <Text style={styles.cusername}>{item.name}</Text>

                    <Text style={styles.cmessage}>
                        {messages?.text ? messages.text : "Tap to start chatting"}
                    </Text>
                </View>
                <View>
                    <Text style={styles.ctime}>
                        {messages?.time ? messages.time : "now"}
                    </Text>
                </View>
            </View>
        </Pressable>
    );
};

export default ChatComponent;
Enter fullscreen mode Exit fullscreen mode

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.

import React from "react";
import { View, Text, Pressable, SafeAreaView, FlatList } from "react-native";
import { Feather } from "@expo/vector-icons";
import ChatComponent from "../component/ChatComponent";
import { styles } from "../utils/styles";

//πŸ‘‡πŸ» The Modal component
import Modal from "../component/Modal";

const Chat = () => {
    const [visible, setVisible] = useState(false);

    //...other variables
    return (
        <SafeAreaView style={styles.chatscreen}>
            <View style={styles.chattopContainer}>
                <View style={styles.chatheader}>
                    <Text style={styles.chatheading}>Chats</Text>

                    {/* Displays the Modal component when clicked */}
                    <Pressable onPress={() => setVisible(true)}>
                        <Feather name='edit' size={24} color='green' />
                    </Pressable>
                </View>
            </View>

            <View style={styles.chatlistContainer}>...</View>
            {/*
                Pass setVisible as prop in order to toggle 
                the display within the Modal component.
            */}
            {visible ? <Modal setVisible={setVisible} /> : ""}
        </SafeAreaView>
    );
};

export default Chat;
Enter fullscreen mode Exit fullscreen mode

Copy the code below into the Modal.js file.

import { View, Text, TextInput, Pressable } from "react-native";
import React, { useState } from "react";
import { styles } from "../utils/styles";

const Modal = ({ setVisible }) => {
    const [groupName, setGroupName] = useState("");

    //πŸ‘‡πŸ» Function that closes the Modal component
    const closeModal = () => setVisible(false);

    //πŸ‘‡πŸ» Logs the group name to the console
    const handleCreateRoom = () => {
        console.log({ groupName });
        closeModal();
    };
    return (
        <View style={styles.modalContainer}>
            <Text style={styles.modalsubheading}>Enter your Group name</Text>
            <TextInput
                style={styles.modalinput}
                placeholder='Group name'
                onChangeText={(value) => setGroupName(value)}
            />

            <View style={styles.modalbuttonContainer}>
                <Pressable style={styles.modalbutton} onPress={handleCreateRoom}>
                    <Text style={styles.modaltext}>CREATE</Text>
                </Pressable>
                <Pressable
                    style={[styles.modalbutton, { backgroundColor: "#E14D2A" }]}
                    onPress={closeModal}
                >
                    <Text style={styles.modaltext}>CANCEL</Text>
                </Pressable>
            </View>
        </View>
    );
};

export default Modal;
Enter fullscreen mode Exit fullscreen mode

The Messaging screen

Copy the code below into the Messaging.js file.

import React, { useLayoutEffect, useState } from "react";
import { View, TextInput, Text, FlatList, Pressable } from "react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";
import MessageComponent from "../component/MessageComponent";
import { styles } from "../utils/styles";

const Messaging = ({ route, navigation }) => {
    const [chatMessages, setChatMessages] = useState([
        {
            id: "1",
            text: "Hello guys, welcome!",
            time: "07:50",
            user: "Tomer",
        },
        {
            id: "2",
            text: "Hi Tomer, thank you! πŸ˜‡",
            time: "08:50",
            user: "David",
        },
    ]);
    const [message, setMessage] = useState("");
    const [user, setUser] = useState("");

    //πŸ‘‡πŸ» Access the chatroom's name and id
    const { name, id } = route.params;

//πŸ‘‡πŸ» This function gets the username saved on AsyncStorage
    const getUsername = async () => {
        try {
            const value = await AsyncStorage.getItem("username");
            if (value !== null) {
                setUser(value);
            }
        } catch (e) {
            console.error("Error while loading username!");
        }
    };

    //πŸ‘‡πŸ» Sets the header title to the name chatroom's name
    useLayoutEffect(() => {
        navigation.setOptions({ title: name });
        getUsername()
    }, []);

    /*πŸ‘‡πŸ» 
        This function gets the time the user sends a message, then 
        logs the username, message, and the timestamp to the console.
     */
    const handleNewMessage = () => {
        const hour =
            new Date().getHours() < 10
                ? `0${new Date().getHours()}`
                : `${new Date().getHours()}`;

        const mins =
            new Date().getMinutes() < 10
                ? `0${new Date().getMinutes()}`
                : `${new Date().getMinutes()}`;

        console.log({
            message,
            user,
            timestamp: { hour, mins },
        });
    };

    return (
        <View style={styles.messagingscreen}>
            <View
                style={[
                    styles.messagingscreen,
                    { paddingVertical: 15, paddingHorizontal: 10 },
                ]}
            >
                {chatMessages[0] ? (
                    <FlatList
                        data={chatMessages}
                        renderItem={({ item }) => (
                            <MessageComponent item={item} user={user} />
                        )}
                        keyExtractor={(item) => item.id}
                    />
                ) : (
                    ""
                )}
            </View>

            <View style={styles.messaginginputContainer}>
                <TextInput
                    style={styles.messaginginput}
                    onChangeText={(value) => setMessage(value)}
                />
                <Pressable
                    style={styles.messagingbuttonContainer}
                    onPress={handleNewMessage}
                >
                    <View>
                        <Text style={{ color: "#f2f0f1", fontSize: 20 }}>SEND</Text>
                    </View>
                </Pressable>
            </View>
        </View>
    );
};

export default Messaging;
Enter fullscreen mode Exit fullscreen mode

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:

import { View, Text } from "react-native";
import React from "react";
import { Ionicons } from "@expo/vector-icons";
import { styles } from "../utils/styles";

export default function MessageComponent({ item, user }) {
    const status = item.user !== user;

    return (
        <View>
            <View
                style={
                    status
                        ? styles.mmessageWrapper
                        : [styles.mmessageWrapper, { alignItems: "flex-end" }]
                }
            >
                <View style={{ flexDirection: "row", alignItems: "center" }}>
                    <Ionicons
                        name='person-circle-outline'
                        size={30}
                        color='black'
                        style={styles.mavatar}
                    />
                    <View
                        style={
                            status
                                ? styles.mmessage
                                : [styles.mmessage, { backgroundColor: "rgb(194, 243, 194)" }]
                        }
                    >
                        <Text>{item.text}</Text>
                    </View>
                </View>
                <Text style={{ marginLeft: 40 }}>{item.time}</Text>
            </View>
        </View>
    );
}
Enter fullscreen mode Exit fullscreen mode

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.

import { View, Text, TextInput, Pressable } from "react-native";
import React, { useState } from "react";
import { styles } from "../utils/styles";

//πŸ‘‡πŸ» Import socket from the socket.js file in utils folder
import socket from "../utils/socket";

const Modal = ({ setVisible }) => {
    const closeModal = () => setVisible(false);
    const [groupName, setGroupName] = useState("");

    const handleCreateRoom = () => {
        //πŸ‘‡πŸ» sends a message containing the group name to the server
        socket.emit("createRoom", groupName);
        closeModal();
    };
    return (
        <View style={styles.modalContainer}>
            <Text style={styles.modalsubheading}>Enter your Group name</Text>
            <TextInput
                style={styles.modalinput}
                placeholder='Group name'
                onChangeText={(value) => setGroupName(value)}
            />
            <View style={styles.modalbuttonContainer}>
                {/* πŸ‘‡πŸ» The create button triggers the function*/}
                <Pressable style={styles.modalbutton} onPress={handleCreateRoom}>
                    <Text style={styles.modaltext}>CREATE</Text>
                </Pressable>

                <Pressable
                    style={[styles.modalbutton, { backgroundColor: "#E14D2A" }]}
                    onPress={closeModal}
                >
                    <Text style={styles.modaltext}>CANCEL</Text>
                </Pressable>
            </View>
        </View>
    );
};

export default Modal;
Enter fullscreen mode Exit fullscreen mode

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

//πŸ‘‡πŸ» Generates random string as the ID
const generateID = () => Math.random().toString(36).substring(2, 10);

let chatRooms = [
    //πŸ‘‡πŸ» Here is the data structure of each chatroom
    // {
    //  id: generateID(),
    //  name: "Novu Hangouts",
    //  messages: [
    //      {
    //          id: generateID(),
    //          text: "Hello guys, welcome!",
    //          time: "07:50",
    //          user: "Tomer",
    //      },
    //      {
    //          id: generateID(),
    //          text: "Hi Tomer, thank you! πŸ˜‡",
    //          time: "08:50",
    //          user: "David",
    //      },
    //  ],
    // },
];

socketIO.on("connection", (socket) => {
    console.log(`⚑: ${socket.id} user just connected!`);

    socket.on("createRoom", (roomName) => {
        socket.join(roomName);
        //πŸ‘‡πŸ» Adds the new group name to the chat rooms array
        chatRooms.unshift({ id: generateID(), roomName, messages: [] });
        //πŸ‘‡πŸ» Returns the updated chat rooms via another event
        socket.emit("roomsList", chatRooms);
    });

    socket.on("disconnect", () => {
        socket.disconnect();
        console.log("πŸ”₯: A user disconnected");
    });
});
Enter fullscreen mode Exit fullscreen mode

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

app.get("/api", (req, res) => {
    res.json(chatRooms);
});
Enter fullscreen mode Exit fullscreen mode

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

const [rooms, setRooms] = useState([]);

//πŸ‘‡πŸ» Runs when the component mounts
useLayoutEffect(() => {
    function fetchGroups() {
        fetch("http://localhost:4000/api")
            .then((res) => res.json())
            .then((data) => setRooms(data))
            .catch((err) => console.error(err));
    }
    fetchGroups();
}, []);

//πŸ‘‡πŸ» Runs whenever there is new trigger from the backend
useEffect(() => {
    socket.on("roomsList", (rooms) => {
        setRooms(rooms);
    });
}, [socket]);
Enter fullscreen mode Exit fullscreen mode

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.

import React, { useEffect, useLayoutEffect, useState } from "react";
import { View, TextInput, Text, FlatList, Pressable } from "react-native";
import socket from "../utils/socket";
import MessageComponent from "../component/MessageComponent";
import { styles } from "../utils/styles";

const Messaging = ({ route, navigation }) => {
    //πŸ‘‡πŸ» The id passed
    const { name, id } = route.params;

//...other functions

    useLayoutEffect(() => {
        navigation.setOptions({ title: name });

        //πŸ‘‡πŸ» Sends the id to the server to fetch all its messages
        socket.emit("findRoom", id);
    }, []);

    return <View style={styles.messagingscreen}>...</View>;
};

export default Messaging;
Enter fullscreen mode Exit fullscreen mode

Create the event listener on the server.

socket.on("findRoom", (id) => {
    //πŸ‘‡πŸ» Filters the array by the ID
    let result = chatRooms.filter((room) => room.id == id);
    //πŸ‘‡πŸ» Sends the messages to the app
    socket.emit("foundRoom", result[0].messages);
});
Enter fullscreen mode Exit fullscreen mode

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

//πŸ‘‡πŸ» This runs only initial mount
useLayoutEffect(() => {
    navigation.setOptions({ title: name });
    socket.emit("findRoom", id);
    socket.on("foundRoom", (roomChats) => setChatMessages(roomChats));
}, []);

//πŸ‘‡πŸ» This runs when the messages are updated.
useEffect(() => {
    socket.on("foundRoom", (roomChats) => setChatMessages(roomChats));
}, [socket])
Enter fullscreen mode Exit fullscreen mode

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.

const handleNewMessage = () => {
    const hour =
        new Date().getHours() < 10
            ? `0${new Date().getHours()}`
            : `${new Date().getHours()}`;

    const mins =
        new Date().getMinutes() < 10
            ? `0${new Date().getMinutes()}`
            : `${new Date().getMinutes()}`;

    socket.emit("newMessage", {
        message,
        room_id: id,
        user,
        timestamp: { hour, mins },
    });
};
Enter fullscreen mode Exit fullscreen mode

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

socket.on("newMessage", (data) => {
    //πŸ‘‡πŸ» Destructures the property from the object
    const { room_id, message, user, timestamp } = data;

    //πŸ‘‡πŸ» Finds the room where the message was sent
    let result = chatRooms.filter((room) => room.id == room_id);

    //πŸ‘‡πŸ» Create the data structure for the message
    const newMessage = {
        id: generateID(),
        text: message,
        user,
        time: `${timestamp.hour}:${timestamp.mins}`,
    };
    //πŸ‘‡πŸ» Updates the chatroom messages
    socket.to(result[0].name).emit("roomMessage", newMessage);
    result[0].messages.push(newMessage);

    //πŸ‘‡πŸ» Trigger the events to reflect the new changes
    socket.emit("roomsList", chatRooms);
    socket.emit("foundRoom", result[0].messages);
});
Enter fullscreen mode Exit fullscreen mode

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

Top comments (53)

Collapse
 
t0nyba11 profile image
Tony B

Don't use sockets for a chat app. Use server-sent events. Much easier to secure, monitor, firewall, ... easier for everything basically.

caniuse.com/?search=server-sent%20...

Collapse
 
codingjlu profile image
codingjlu

I don't see why just because SSE exists means you shouldn't be using websockets. Websockets are designed for the specific purpose of client-server-client messaging, and it's good at that. It's also not difficult to secure or firewall at all, and I don't get your point about SSE being easier to monitor. Care to expound?

Collapse
 
t0nyba11 profile image
Tony B

Easier to monitor in terms of tools in azure, third party tools like datadog, etc. So many tools work so easily with http calls, and you will have a lot less problems with firewalls inside corporate networks and networks you don't control. Just an overall more stable system. I have worked on a large system that switched from websockets to SSE and could never go back.

Thread Thread
 
nevodavid profile image
Nevo David

I think it's time to make an article about SSE, it's not as popular as Websockets and it's a shame :)

Collapse
 
rinat profile image
Rinat Shay.

Use server-sent events

... and then send messages from client to server using REST API calls, or what? It will be slow as hell, especially for chat-like applications. There is absolutely no problems with securing, monitoring or "firewalls" (whatever you mean by that) in WebSockets.

Collapse
 
t0nyba11 profile image
Tony B • Edited on

LOL ... "slow as hell" ... right, you obviously haven't thought that through, unless you are typing at a rate of hundreds of messages per second. You might want to check how other applications like Google's Chat works inside the Gmail website.

Collapse
 
rinat profile image
Rinat Shay.

I'm actually should note that there is a problem with WebSockets β€”Β load balancing, but since you haven't mentioned that in a first place, I can assume you haven't run WS on scale.

For everyone here: base your technical decisions on your own (or someone else's) researches. Not by categorical statements in comments from strangers.

Thread Thread
 
t0nyba11 profile image
Tony B • Edited on

Load balancing is the least of your problems with WS. An open connection via HTTPS for SSE has the same issue in that regard. The only applications that really need websockets are real-time games, almost everything else is better off with SSE. And your assumption is wrong. ;)

Collapse
 
t0nyba11 profile image
Tony B

What do you do as a developer the first time a customer calls and says the Chat feature in your application isn't working, but everything else is working fine?

Also, re: "firewalls" ... Firewall rule engines and security settings are largely designed around HTTP routing, not lower-level websocket connections.

The tooling is just nicer for SSE compared to WebSockets at every level.

But each to their own. :)

Collapse
 
nevodavid profile image
Nevo David

Hard about it a few times!
Any good wrapper library for that like Socket.io?

Collapse
 
t0nyba11 profile image
Tony B

No need for a wrapper, they will work in a couple of lines of code, with small changes to the client and server. Lots of examples floating around...

w3schools.com/html/html5_serversen...

Thread Thread
 
liviufromendtest profile image
Liviu Lupei

That's awesome!

Collapse
 
liviufromendtest profile image
Liviu Lupei

Thank you for the tutorial!

Alright, so Novu is open-source, but how will it generate revenue?

Because it seems like it's a for-profit company from Israel, and not just "some cool guys from the community".

crunchbase.com/organization/novu-1a6b

I've seen open-source solutions where the developers install it and integrate it deep into their system, and suddenly some features become Enterprise-only and the company has to pay or go through the painful process of removing that solution from their stack.

Collapse
 
combarnea profile image
Tomer Barnea

Hey Liviu :)

My name is Tomer, I am one of the co-founder of Novu, and hopefully I am also a "cool gut from the community" πŸ˜…

First of all you are correct, Novu is indeed a commercial solution as well as an open-source standard. I think you dead on about what you wrote about monetization, the way we try and build to solution is so it will be clear as to what's going on to be an open-source offer, and what's going to be a commercial one, so no one will implement an open-source library and then be surprised by a "hidden" license or a feature that was pay-walled all of a sudden.

As both my co-founder and my self, and most of the team as well for that matter, are developers we take that very seriously, I would love to have deep conversation about that.

I believe if an individual or company chose to use Novu as an open-source solution this is perfectly fine, and they are making the whole community and the open-source standard stronger by being a part and suppling valuable feedback.

As mentioned I happy to jump on call and get some more ideas and thoughts as to how to do it properly :)

Collapse
 
nevodavid profile image
Nevo David

Have you ever build a chat app before?

Collapse
 
varshithvhegde profile image
Varshith V Hegde • Edited on

I have built something like this but it was using firebase and java

Collapse
 
nevodavid profile image
Nevo David

Just wrote you on the other thread ❀️

Thread Thread
 
varshithvhegde profile image
Varshith V Hegde

Yeah πŸ‘

Collapse
 
nevodavid profile image
Nevo David

Do you have anything to share? 🀩

Thread Thread
 
varshithvhegde profile image
Varshith V Hegde

github.com/Varshithvhegde/Whatsapp... this is the project in my intermediate days of learing android development so it's not so super working but still it's great πŸ˜…πŸ˜‚

Collapse
 
eminarium profile image
Merdan Durdyyev

Awesomely constructed story blog. I honestly loved it, Nevo.
Keep writing such interesting and detailed posts.

Collapse
 
nevodavid profile image
Nevo David

Wow, Thank you very much @eminarium !

Collapse
 
varshithvhegde profile image
Varshith V Hegde • Edited on

Awesome project broπŸ”₯⚑✨

Collapse
 
nevodavid profile image
Nevo David

Thank you very much @varshithvhegde :)
Share your chat project when you can :)

Collapse
 
varshithvhegde profile image
Varshith V Hegde

Yeah sure github.com/Varshithvhegde/Whatsapp... this my project it's just like a whatsapp chatting still have some flaws thoughπŸ˜…

Thread Thread
 
nevodavid profile image
Nevo David

That's awesome! If you want some contribution, just join our community!
People would love to help you out!
discord.gg/C2yvXfpQ

Thread Thread
 
varshithvhegde profile image
Varshith V Hegde

Yeah sure i would love to πŸ‘

Collapse
 
nevodavid profile image
Nevo David

How are everybody today? Did you register to HackSquad yet?

Image description

Collapse
 
modelhusband01 profile image
Model Husband πŸ‘‘

Wow, this is Awesome

Collapse
 
nevodavid profile image
Nevo David

Thank you Model Husband!
Let me know if you use it :)

Collapse
 
dhruvjoshi9 profile image
Dhruv Joshi

Such a detailed and helpful blog!!! Thanks!!

Collapse
 
nevodavid profile image
Nevo David

Thank you very much @dhruvjoshi9 🀩
Let me know if you read it :)

Collapse
 
iampearceman profile image
Pearceman

Keep up the good writing!

Collapse
 
nevodavid profile image
Nevo David

Thank you Emil!
Happy Holidays!

Collapse
 
sauain profile image
Saurav Jain

Amazing writing :)

Collapse
 
nevodavid profile image
Nevo David

Thank you 😍😍

Collapse
 
sohrab09 profile image
Sohrab Hossain

Thank you for this amazing blog 😍

Collapse
 
nevodavid profile image
Nevo David

Happy that you like it πŸ₯°
How are you today? :)

Collapse
 
nevodavid profile image
Nevo David

Do you think React native is a good alternative to Swift / Java(Androind)

Collapse
 
rutamhere profile image
Rutam Prita Mishra

It is a great tutorial. Absolutely loved reading this. πŸš€

Collapse
 
nevodavid profile image
Nevo David

Thank you so much Rutam!
How are you today?

Collapse
 
lamhoanghg profile image
Lam Hoang

very detail! keep up

Collapse
 
nevodavid profile image
Nevo David

Thank you, Lam!
Let me know if something’s missing!

Collapse
 
mouseroot profile image
Mouseroot

This is amazing work, when I get the chance I will use this post to re-visit react and react -native.

Collapse
 
nevodavid profile image
Nevo David

Awesome!
Let me know if you find some free time for it!

typescript

11 Tips That Make You a Better Typescript Programmer

1 Think in {Set}

Type is an everyday concept to programmers, but it’s surprisingly difficult to define it succinctly. I find it helpful to use Set as a conceptual model instead.

#2 Understand declared type and narrowed type

One extremely powerful typescript feature is automatic type narrowing based on control flow. This means a variable has two types associated with it at any specific point of code location: a declaration type and a narrowed type.

#3 Use discriminated union instead of optional fields

...

Read the whole post now!