Build a chat app with React, TypeScript and Socket.io
Last updated: Oct 24, 2024
This is going to be a thorough step-by-step guide for building a single page chat application using React, TypeScript and Socket.io.
If you want to skip the reading, here is the GitHub repository with a detailed README, and here you can check the live demo. In order to play with it, you need to open it in two different browsers (or browser tabs) or devices (you may use your computer and your smartphone) and chat with each other.
Research
When you are about to start a new project, it’s a good practice to do initial research about the technical stack you are planning to use.
In other words, you may want or need — especially if you don’t have prior experience with it — to investigate on each technology you will be using. I recommend doing that separately. Take one of the them and create a small app that you can play with.
If you need to check how the integration of two or more technologies is going to work in a real project — then you might want to include them all together in your “research-test-play” app — but preferably do your research one at a time.
Getting to the point
When I started thinking about making this chat application, I did exactly what I described above. I haven’t had recent experience with TypeScript and none with Socket.io, so I had to take a look at those and get myself familiarized with what is their current state. As my plan was to use React as a main UI library, I needed to see how it was going to work with the other guys in the equation. So I did.
I created two small applications (repos here and here) with these technologies, just to be able to play with them and learn how could I use them in my future chat application.
After my initial research was done I was able to start thinking and planning the implementation of my main chat app.
Photo by Hutomo Abrianto on Unsplash
High level planning
Usually what people mean when they say “high level plan” is that they are looking for the big picture. Which means we need to create a rough plan of our execution and define our main pillars, but without going into too much detail. Now when we have clear idea of what to do, let’s start doing it! ?
Note: From this point forward I will be assuming that you are following my steps as I describe them, hence I will be writing in the second person. ?
Tech stack
We already mentioned the main technologies we will be using, but let’s define a proper list of all of them here:
Application Features
In this section we will describe what the features of our application are going to be.
Every time when we plan a new project, we must define certain criteria which will describe some level of completion when met.
In other words, we need to set a limit point which, once reached, will show that our project is completed or at least in its first version. There is a famous saying, that could be matched to the issue with the “never ending” projects:
A good plan today is better than a perfect plan tomorrow.
Here is my list with the features I wanted to implement initially:
Header
Chat page
Settings page
ImprovementsAt the time of writing this, there are still some pending features I would like to implement. Below is the list of all improvements I did or plan to do in future (the ones with the thumb emoji are already implemented):
When we know the initial plan and the requirements we need to fulfill, we can do our high level analyses. Our app will have two pages, Chat and Settings, accessible via tab controls.
The Chat page will contain the main chat area with the controls needed to send messages (input field and a button).
The Settings page will contain a few controls for selecting the options described above.
With that in mind we can go to the next section where we will create a more detailed plan before the actual implementation.
More detailed planning
In this section we need to have a deeper look at our application and define what will be the building blocks for it. Since we are going to use React and we know that in the React world the term component is widely used, we may refer to our building blocks as components. We will have components responsible for purely visual stuff, as well as such for managing the local storage, for example.
Let’s try to imagine mentally how our app will look in the end and what components it will need. What we already know is this:
Server part
We will need an HTTP server that will take care of starting the server and handling interactions with Socket.io (sending and receiving messages). Our server logic will be simple enough to live in only one file. You can see the actual implementation here.
Client partHere we need to have all the visual controls, plus means for managing interactions with local storage, where we will save the user preferences, as well as handling of the translations and color themes.
Now is a good moment to point out that for implementing the translations and theming functionality in the app, I have used React Context API. Also, since I knew I would have to deal with Local Storage, I did another round of the “research-test-play” trip. And the output of it was that I already had a nice service, which provides all the functionalities I needed.
Another thing you will notice when looking at the components folder is that every component has its own directory with a few files in it.
These files serve the following logic:
index.ts → entry point, just expose the component itself. This helps for not having to write repeatedly and long import statements. Here is an example:
// Instead of having to write this:
import ChatArea from '../../ChatArea/ChatArea';
// We can have just this:
import ChatArea from '../../ChatArea';
<ComponentName>.tsx (ChatArea.tsx) → actual component implementation live here.
<ComponentName>.test.tsx (ChatArea.test.tsx) → unit tests of the component live here.
<StyledComponentName>;.tsx (StyledChatArea.tsx) → CSS styles of the component live here.
The same pattern is used for most of the components, exception are only the pages, such as the components which play the role of parents for all the inner parts — ChatPage and SettingsPage.
So, with that said, I think we can see what would be our application structure when we try to “componentize” it. Here a list of the components I came up with:
Chat application components
Note: all names are a matter of personal choice, feel free to name yours as you wish.
Let me try to give you a bit more detailed explanation for each of them below:
ChatArea component
ClockModeSelector component
LanguageSelector component
Message component
MessageSender component
Navigation component
Nickname component
ResetButton component
SendingOptions component
ThemeSelector component
Timestamp component
UnreadMessagesCounter component
Everything described above was related to our React components. All of them are responsible for getting some data and displaying it in a proper way. In order to be able to handle this data in a convenient for us way, we use a few more things. Let’s have a look at these things in the sections below.
Redux State Management
Here we will talk about how our app state is being managed by using Redux and socket middleware.
Store
Our store is going to be relatively simple. We will have only two reducers defining a piece of the state reserved for the socket state and for the messages state. This is also where we apply our middleware. If you are familiar with Redux Saga package, you have probably seen this pattern of applying custom middleware when using Redux.
Something like this:
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import reducer from './reducers'
import mySaga from './sagas'
// create the saga middleware
const sagaMiddleware = createSagaMiddleware()
// mount it on the Store
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
)
But in our case it would be like this:
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import socketReducer from './socket/reducer';
import messageReducer from './message/reducer';
import socketMiddleware from './socket/middleware';
const rootReducer = combineReducers({
socketState: socketReducer,
messageState: messageReducer
});
// @ts-ignore
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const index = {
...createStore(rootReducer, composeEnhancers(applyMiddleware(socketMiddleware)))
};
export default index;
Message
After defining our store, it’s time to see how are we going to handle the messaging in Redux. We have defined our actions here and our messageReducer here.
Socket
We follow the same logic as above here. We have our socket actions, the middleware I mentioned above, and the socketReducer.
Theming
In order to implement the possibility for setting different color themes in our application and considering the fact we are using styled-components, I used a ThemeProvider — component provided by them. Here is the implementation that includes defining objects with custom colors used in the themes.
The logic behind applying the selected color theme resides here. Ideally the containing component should be named something different than TranslationsProvider, as it doesn’t handle only the translations, as we see. We could add this to the list of future improvements/refactoring.
Here is how the existing color themes look:
Utilities
In almost every software project, at certain point, the need of common reusable functions emerges. This the moment when developers usually create a common shared file or files, containing such helpers functions. In our case this would be /utilities folder that currently contains four files. I will go through each of them below and explain the logic behind my decision to create it and put it there:
Here is an example of how is it used:
export default withTranslations(SettingsPage as React.FunctionComponent);
We have walked a long way to here and we still haven’t started with the actual implementation.
That is a vivid pointer for us to show how important and extensive the planning phase of a project could be.
Let’s jump now to the implementation phase in the next section.
Implementation
If you reached this point of the tutorial, you should have a very clear idea of what are we going to build. Here, we are about to find out how are we going to do it.
Starting small
As with any other project we should strive to start with small, incremental chunks and build on them. In our case I have decided to start first with building the header navigation. The reason for that was that I wanted to have the router and the navigation controls in place, so I could easily navigate through the tabs while developing and testing.
Settings page
After I had finished with the header and navigation parts, I decided to jump to the settings page first. Again, my reasoning was very simple — I wanted to build first what I was going to use in the Chat page. In other words I wanted to be able to customize my chat area, messages, ways of sending and so on, before implementing them.
So I started building component by component as I described them in the previous section. Once I had the full Settings page finished, I was able to go and start implementing the Chat page components. But before that, I had to take care of the supporting stuff — integrating with local storage and adding translations mechanism.
Chat page
After I have done all from above, the implementation of the Chat page and its components was fairly easy. I had to take care of the visual part manly and make the integration with the Redux store. As you already saw, I had to implement only two components which are shown and used on the Chat page — ChatArea and MessageSender.
Adding improvements
I want to say a few words here regarding the app improvements we did or will do in the future. Usually when we have a new requirement (let’s call it “requirement”, that makes is sound closer to what would be in a real project), it is again a very good idea to do some initial research, instead of jumping directly into implementation. You will be surprised how many solutions are already out there, waiting for us to use them.
In other words, we don’t have to invent the wheel again.
This is what I did when I started thinking about adding support for emoticons or link parsing. It turned out that there are already solutions I could use with a little tweaking from my side, just to make them fit well in my project.
Here are the links to the packages I used:
And here you can see how I used them in our chat app.
Deploying to Heroku
I have written another article in the past. It was about totally different subject, but there is a part exactly related to how to deploy an app to Heroku. You might find it useful to check it out.
For deploying our chat application to Heroku, I will assume you have already an account and can easily follow the steps below:
Future (possible) plans
At the time of writing this I was thinking it might be very interesting to try building the same application with the other super famous UI library on the market — Angular. I still think it will be worth it, but I am not sure whether I will have the time and the power to do it ?.
In any case, what I am thinking about it as a pure, technical comparison of two major UI libraries from the developer’s point of view.
If I do it, I will make sure you know it!
Thanks for reading.
← Go home