Introduction:

smartchat is a responsive web chat app built with React.js. It offers users the ability to exchange messages in existing chat channels and create new chat channels as desired. Users must enter their names in order to join the chat app. smartchat’s data is exported to and fetched from Firebase.

I built smartchat as an experimental project, to learn React.js. Before React,js, I worked with AngularJS, to build another web music player app called Bloc Jams. The learning curve of AngularJS was definitely higher because it is a full, powerful framework. The set up code for AngulaJS is more complicated, as it requires an application and a controller , as well as definitions for any directives I am building. However, because AngularJS has been around for longer, it has a huge support community and good documentations, which enable me to learn it better.

React.js, on the other hand, was much easier to learn. It has more convenient architecture compared to AngularJS’s MVC. And since I’m already familiar with HTML, it was quite straightforward to use JSX when building the chat app. I like that ReactJs renders only what’s changed, so it is perfect for a chat app that changes constantly and requires rendering a good amount of data. ReactJS also eliminates direct interaction with the DOM and obviates the need for a number of dependencies like jQuery.

In this case study, I want to go over my process of building smartchat and what I learned from it. I got most of my reading resources from https://facebook.github.io/react/, https://egghead.io/technologies/react, https://platform.github.community/ and https://stackoverflow.com. Unlike AngularJS, the resources for React.js are not as abundant so it was more time consuming for me to find answers to some of my questions.

Project User Stories:
  1. As a user, I want to set my username.
  2. As a user, I want to see a list of available chat rooms.
  3. As a user, I want to see a list of messages in each chat room.
  4. As a user, I want to send messages associated with my username in a chat room.
  5. As a user, I want to create chat rooms.
Channels and Messages:

I put together arrays of channels and messages to have something to start with. Each channel has an id and a name prop. Each message has an id, an author, a text, and a channel_id prop. Usually people would set up Firebase first, and get data from there. But as a first time React.js learner, I wanted to keep the learning process to just React before learning about Firebase.

Now that I had a list of channels, and a list of messages, I needed to display them. So I created the <MessagePane> and <ChanneList> components and map through them. I applied React.Component and stateless functional component concepts that were extremely helpful for the remaining process of building smartchat. I also learned when to use React.Component:

  • I need local state (this.setState)
  • I need a lifecycle hook (componentWillMount, componentDidUpdate, etc
  • I need to access backing instances via refs

Using typechecking with PropTypes, I added an extra step to verify the default prop for channels is an array and must be passed to <ChannelList> component. I did the same thing with messages in <MessagePane>. Prop validation is useful for when things get messed up, React will give an error message in the console, saying which props is wrong/missing plus the render method that caused the problem.

Next, I created the <Form> component to enable users to type and send new message. I wrote an onSubmit() function and an updateMessage() event handler. The onSubmit() function has the default prop onSend() which is an arrow function and must be passed to the component. This onSend() function read new message text when a user submit the form. Then I put the Form component inside <MessagePane>. In the App.js, I added an onSendMessage() function to create a new_message when a user click the Send button and add new_message to the array of messages.

For <ChannelList>, I added an onAddChannel() function to window.prompt a pop up modal for user to create a new_channel. Then it will be added to the array of channels and saved to Firebase. I could have used React boostrap modal, but I decided to try different approaches and used window.prompt. It is a lot less code but very limited when it comes to styling and customization.

Setting up <ChannelList> and <MessagePane> taught me the concept of React key. Without the key property, React can’t identify which channels or messages have changed, are added, or are removed.

My next task was to filter messages to match the channels they belong to. When a user selects a channel, only messages in that channel will be rendered. In order to achieve this task, I added an onChannelSelect() function and filterMessage() function. In onChannelSelect() function, I set the state of select_channel_id to be the id of the channel selected. In filterMessage(), I put channel_id, which is plain old JS object, inside curly brackets as a parameter in an arrow function that returns channel_id equals the most current state’s selected_channel_id.

Users must enter their names to access the chat app, so I needed to add a sign-in form when the app loads. To achieve this, I used React bootstrap modal that I installed https://www.npmjs.com/package/react-modal. I added an onAddAuthor()  function in the App.js to capture the user/author so that when that user/author sends a message, his/her name will be attached to new_message.

Firebase:

It’s time to integrate Firebase with the app. This is where I struggled the most to get React and Firebase to talk. For example, when I clicked the submit button to add a new channel, I didn’t realize I was executing a query to add a new channel locally instead of a query to add a new channel in Firebase. I didn’t specify in my codes the current state of channels are no longer the data from local storage; it is now data from Firebase. After a lot of reading and researching, I found a solution. I had to write and export getMessages(), getChannels(), saveMessages(), saveChannels(), onNewMessage(), and onNewChannel() functions in storage.js to grab data from Firebase then use React promises to handle asynchronous computations.

React Router v4:

The process of integrating React Router v4 into my chat app cost me many cups of coffee and hours surfing the web, but it made me think more on how I should strategically structure my app. To get started, I installed react-router-dom and history. I chose to configure my app with <BrowserRouter> because it is more preferred and my web app is not hosted on a server that only serves static files. Then I mapped through channels and return the <NavLink> component with path “/channels/${name}”. I used <NavLink> instead of <Link> because I want channels’ names to behave as menu items.

In my App.js, I added a <Switch> component inside <BrowserRouter>. According to React Training documentation, <Switch> renders the first child <Route> or <Redirect> that matches the location. I had 3  <Route> components. One  has exact path of  “/” and renders the <Intro> component aka the home page. Another one has path of “*” and renders the <NotFound> component when nothing matches the location. The other <Route> component has path of “/channels/:name”, which renders a render that loops through all the channels’ names to see if they match the URL’s path names. If one of them matches, the <MessagePane> component will be returned. If not, <NotFound> component aka my 404 page  will be returned. Before I was able to achieve this conditional render, I used React Router v4’s “match” property incorrectly and had the most difficult time trying to figure it out. I had to ask my mentor for help. He showed me how to get the name of the location using props.match.url.split(“/”) inside <Route>’s render. From there I created a hasName() function that check if any item of channels’ names array matches.

After successfully accomplishing the task above, I had to figure out a way to load just messages that has channel_id that matches the id property of channels. Similar to hasName(), I created a getId() function to get the id of the channel that has name that matches the URL’s props, and set channel_id in messages to be the id returned from getId()

In addition, I also needed to tweak my onSendMessage() function to make sure that I push messages with the correct channel_id to Firebase. So I used the same getId() function, but this time I compare channels’ names to the name of the clicked channel’s URL, which is the result of window.location.pathname.split(“/”)[2]. To set the correct channel_id for new_message, I put a conditional statement inside onSendMessage() to set the channel_id of each channel based on whether it was clicked or not.

react-cookie and react-sidebar:

The last few steps I had to do was adding react-cookie and react-sidebar to improve the overall use and look of the app. I wanted to make smartchat mobile optimized so react-sidebar was the perfect solution because I wouldn’t have to use too much CSS. I added validation to the sign-in form as well to make sure users put in their names and if they don’t they will get an error message. Inside the onAddAuthor() function, I added a conditional statement that if author.length is shorter or equals 0, the error message will show. To complete the app, I added a <Scroll> component to make the page always auto scroll to the newly added message. I accomplished this task by using ReactDOM findDOMNode and ref. These processes taught me how to effectively use packages and integrate them with the rest of my app.

Wrapping up:

Building smartchat took about 3 weeks. I am proud of it, even though it is simple and could have more complex features. I found building a web app is a great way of learning a framework or a library. With minimal guidance, I had to do a lot of browsing through stackoverflow, github, and documents to find solutions to my problems. I learned many fundamental and important React.js, JSX, ES6 concepts while at the same time revisited a lot of previous JavaScript and CSS concepts. I definitely would want to revisit smartchat in the future and add more features to it, and maybe refactor it using a different framework I haven’t worked with before. You can visit smartchat live at https://smartchat-itshamy.herokuapp.com/, or folk me on Github https://github.com/itshamy/smart-chat