In part 2, we built the backend of our chat application, which includes the functionality for real-time multilingual translations. In this part, we will focus on the front end, and specifically how to render all the messages in each conversation, and how to use Socket.IO to get real-time functionalities.
We developed a beginner-friendly approach that uses only React hooks (useState, useContext) for state management, TypeScript, and CSS Tailwind.
In our Chat App, we use React Router DOM to control all the routes. This allows us to easily navigate users between different parts of our application.
We have three main routes in our application:
/: This route is for user registration and login.
/chat: This route is for the main chat page.
/profile: This route is for user profile updates.
We use the useNavigate hook to navigate users between these routes. This hook makes it easy to navigate to a specific route or to go back to the previous route.
const App = () => {
const navigate = useNavigate();
useEffect(() => {
if (localStorage.getItem("token")) {
navigate("/chat");
} else {
navigate("/");
}
}, []);
return (
<>
<div className="flex flex-col h-screen ">
<Navbar />
<Routes>
<Route path="/" element={<Main />} />
<Route path="/chat" element={<Chat />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</div>
</>
);
};
We'll skip over the sign-up and login functions and concentrate on the core aspect of handling messages with SocketIO. If you'd like to explore the complete codebase, you can find it in our repository.
Once a user logs in, we retrieve and store their data using the useContext hook. This allows us to seamlessly integrate user information into the chat interface.
const {
user,
conversationId,
setConversationId,
selectId,
setSelectId,
isDarkMode,
setRecipient,
messages,
language,
setLanguage,
} = useContext(UserContext);
Connecting to the Socket.IO Server
In this part of the code, we're using useRef to store the socket instance. This avoids unnecessary re-renders when the component updates. The socket is connected to the backend URL using the io function from the socket.io-client library.
const socket = useRef<Socket<MyEventMap> | null>();
const [usersList, setUsersList] = useState<UsersList | null>(null);
const [onlineFriends, setOnlineFriends] = useState<User[]>([]);
const [onlineUsers, setOnlineUsers] = useState<string[]>([]);
const [view, setView] = useState<"friends" | "people">("friends");
first of all, we need to import socket io client and instantiate the Socket. The socket will be connected to your back end, it could be “http://localhost:8000”. In our case, we connect it to our backend URL which is deployed on Render
useEffect(() => {
socket.current = io(`${DOMAIN.BACKEND_DEPLOY_URL}`);
}, []);
Next, we would like to have insights into who's online and who's not.
Creating a Living Connection from Front End
When a user logs into the application, they connect to our application through a Socket.IO socket, then a simple command, socket.emit("addUser"), is executed on the frontend. This sends a clear signal to the backend that a new user has entered.
Server-side Magic with "addUser"
On the backend, the server is on the lookout for the "addUser" event with socket.on("addUser"). The server records all of the newly arrived users in an array.
The backend then emit back an event called "getUsers." This event is broadcasted using io.emit("getUsers")
Frontend Listening to the Pulse of "getUsers"
On the front end, we use socket.on("getUsers") so we will always listen to the "getUsers" event. Whenever a new event reaches the front end, the online user list is automatically updated.
Backend
socket.on("addUser", (userId: any) => {
onlineUsers.set(userId, socket.id);
io.emit("getUsers", Array.from(onlineUsers));
});
Frontend
useEffect(() => {
if (socket.current && user) {
socket.current.emit("addUser", user._id);
socket.current.on("getUsers", (users: unknown[]) => {
let usersMap = new Set();
users.map((user: any) => {
usersMap.add(user[0]);
let usersArray: any[] = Array.from(usersMap);
setOnlineUsers(usersArray);
});
});
}
}, [socket.current, user]);
To put it simply, when we work on the part of a website that you see, there are two main ways we use:
Front end
- Sending Messages: Imagine you want to tell the backstage of the website something. To do that, we use something like a magic messenger called "socket.emit." It's like sending a secret note. You can give your note any name you like. For example, I use "addUser" to say a new person joined. But you could also say "newUser," just remember to use the same name backstage and frontstage.
- Listening for Messages: Now, when the backstage talks back to us, we use "socket.on" to listen to the messages. All these messages need the same name, so we know what they mean and which note we are talking about.
Backend
However, when we go backstage, things are a bit different:
-
Sending Messages: Now, we have two choices: "socket.emit" or "io.emit." It's like choosing how to shout out news to everyone.
- socket.emit: This's like telling something directly to one person in the crowd.
- io.emit: This's like shouting out the news so everyone in the crowd can hear.
Listening for Messages: Here, we still use "socket.on" just like in the front end.
So, to wrap it all up, remember these simple tricks to make the front and backstage of the website chat smoothly and share all the important news!
In our app, we went with io.emit. We'll tell you more about why in the next post.
For all the details about our project, check out our place on the internet: https://github.com/Talckatoo.
Keep an eye out for Part 4, where we'll bring you more cool stuff!
Top comments (0)