Terug naar alle berichten
Een React Chat-app maken met Firebase, React Router & Authenticatie (deel 2)
Development

Een React Chat-app maken met Firebase, React Router & Authenticatie (deel 2)

Geupdate op februari 5, 2024

This post will continue to finish the actual chat part on top of our previous React Firebase setup from part 1. But it’s finally here!

Our firebase chat app will use the real-time database from Firebase and not the Cloud Firestore. Why? Because the real-time database updates our app automatically for us, so we don’t need to do anything special to make it work!

You can find a working example of our app on chat.weichie.com (unavailable) and you can find the complete project on Github as well.

React chat app with firebase mockup

Where did we left our React Chat application after part 1?

I created this app more than 2 years ago and in the meantime over 1000 people registered to test the chat app! Apologies to everyone who was waiting on part 2, but a recent comment on the Part 1 article made me realize that my blog has more traffic than I thought.

So here we go! Feel free to refresh my mind in the comments if I am talking nonsense in this post. I am no longer using React for my applications but switched it for Vuejs instead.

Using authentication in our Router

Only registered users will be able to post chat-messages in our application. Otherwise… We don’t know who said something. To make sure users are logged in, we’ll change our Router component like this:

// index.js
...

<Router>
  <div className="app">
    <nav className="main-nav">
      {!this.state.user && 
        <div>
          <Link to="/login">Login</Link>
          <Link to="/register">Register</Link>
        </div>
      }

      {this.state.user && 
        <a href="#!" onClick={this.logOutUser}>Logout</a>
      }
    </nav>

    <Switch>
      <Route path="/" exact render={() => <Home user={this.state.user}/>} />
      <Route path="/login" exact component={Login} />
      <Route path="/register" exact component={Register} />
      <Route component={NoMatch} />
    </Switch>
  </div>
</Router>

...

We changed a few things in our index.js-file to check authenticated users. Let’s start with the navigation on top.

We create a navigation element main-nav with 2 navigation elements. One that will be displayed when our user is logged in, and another navigation that will be displayed when the user is logged out. So he can choose if he wants to log in with an existing account or register a new account (and talk with himself)

The other important thing we changed in our index.js file is that we swapped the {home} component with a function, that renders our component in-line. This way we’re able to pass our logged in user as a prop to our home component.

The Home Component

Our Home component is the home screen of our application. This screen will show you the chat-app when you are logged in, or the option to create your account if you are logged out.

First the setup: Now that our router passes our user-state to our Home component, we can display authentication-based elements in this component as well. Let’s see what it looks like:

// Home.js
import React from 'react';
import {Link} from 'react-router-dom'; <-- add this line

class Home extends React.Component{
  render(){
    return(
      <div className="home--container">
        <h1>Welcome to the chat!</h1>
        {this.props.user && 
          <div className="allow-chat">
            We insert our chat-component later
          </div>
        }
        {!this.props.user && 
          <div className="disallow-chat">
            <p><Link to="/login">Login</Link> or <Link to="/register">Register</Link> to start chatting!</p>
          </div>
        }
      </div>
    );
  }
}
export default Home;

In our home render function, we added an if-statement to see whether or not our users are logged in. If so, we are allowed to show our chatbox. If not, we give them the option to log in or to create a new account.

Make sure to import {Link} at the top of our Home.js file.

Chatbox.js – Where the magic happens

The most important file! Apologies again if some of you waited so long for me to finally finish up Part 2 as well… a sincere apology from my end!

So Let’s dive right in and create a Chatbox.js file inside our Home folder. To clarify, this is what our folder structure inside /src/ looks like:

  • /components
    • /Auth
      • Auth.css
      • Login.js
      • Register.js
    • /Home
      • Chatbox.js
      • Home.css
      • Home.js
  • firebase.js
  • index.css
  • index.js

And then we’ll add the usual defaults for a new component:

import React from 'react';

class Chatbox extends React.Component{
  render(){
    return(
      <div className="chatbox">
        <ul className='chat-list'>
          <li>Here we show our chat messages</li>
        </ul>
      </div>
    );
  }
}
export default Chatbox;

Nothing too crazy in here yet. Just a simple component that, for now, will return a list with 1 static element. Next: Add firebase! We will create a component-state into Chatbox.js where we will insert all the messages that we’re pulling from firebase.

So what do we need? A constructor for our state that will store the firebase messages in our app. And a call to firebase to get all the messages in our application. This function will be run when our Chatbox component is mounted. Here’s the setup code:

// Chatbox.js
...
class Chatbox extends React.Component{
  constructor(props){
    super(props);
    this.state = {
      chats: []
    }
  }

  componentDidMount(){
    console.log('Chatbox.js is mounted!');
  }

  render(){
    ...
  }
}

We have a constructor with a state for our ‘chats’ and if we can see ‘Chatbox.js is mounted!’ in our console, we’re ready for the firebase integration.

The Firebase Realtime Database works with ‘refs’. Although it returns one huge JSON tree, refs will give us easy access to the data we need. Our chat app is still empty at the moment, so we need to add some test messages first before we can display our chat list.

Let’s add some messages! Back to our Home Component

To ease up development, it would also be nice if we could see our Chatbox component in our app. So let’s hook it up really quick:

// Home.js
import React from 'react';
...
import Chatbox from './Chatbox'; <-- Add this line

...
  // Update our render method:
  <h1>Welcome to the chat!</h1>
  {this.props.user && 
    <div className="allow-chat">
      <Chatbox />
    </div>
  }
...

This adds the Chatbox component when our user is logged in so we can see what we’re doing. Let’s continue:

We can’t pull an empty list from Firebase. I mean… we can… but we won’t see anything. So head back to our Home.js component and we’ll write some logic to add messages to our database.

// Home.js
import React from 'react';
import firebase from '../../firebase'; <-- Add this line
import {Link} from 'react-router-dom';

class Home extends React.Component{
  constructor(props){
    super(props);
    this.state = {
      message: ''
    }
  }

  handleChange = e => {
    this.setState({[e.target.name]: e.target.value});
  }

  render() {
    ...
    <div class="allow-chat">
      <form className="send-chat" onSubmit={this.handleSubmit}>
        <input type="text" name="message" id="message" value={this.state.message} onChange={this.handleChange} placeholder='Leave a message...' />
      </form>
      
      <Chatbox />
    </div>
    ...
  }
}
export default Home;

What’s happening? We created a state in our Home component and added a form in our ‘send-chat’ div. In React/Vue/Angular/… you are not allowed to have unbound forms. So our input-field has an onChange method that will keep the value in our input field in sync with the input field value in our state.

I already made you import firebase at the top of our file and gave our chat-form the onSubmit function, because we’re continuing our send-message logic right away:

// Home.js
...
handleSubmit = e => {
  e.preventDefault();
  if(this.state.message !== ''){
    const chatRef = firebase.database().ref('test');
    const chat = {
      message: this.state.message,
      user: this.props.user.displayName,
      timestamp: new Date().getTime()
    }
		
    chatRef.push(chat);
    this.setState({message: ''});
  }
}
...

The handleSubmit function goes above our render() method. Like how we did the handleChange function. When submitting the form, we check if the message is empty. If so, we don’t do anything (otherwise people can push empty values to your database – talking from experience…)

If we have a value to push, we create a chatRef. This one will be a firebase database reference. Note that this can be anything we’d like. In my example I used ’test’ as ref, create a chat message object and push it to our chatRef. Then we also update our state, so the message field get cleared once the message is submitted.

The ‘chat’ object we’re pushing to firebase looks like this:

  • message: Our state.message, the message that the user wrote
  • user: The username from the user. We passed our user as a prop to the Home component, so we can access it like this
  • timestamp: A general javascript timestamp, to know when people are posting messages

When you hit submit, you should be able to see our ‘Test’ ref and our message in the Realtime database! Booyaa.

If the test is working, you can change the ref in the const chatRef = firebase.database().ref('test'); line from ’test’ to whatever you want. As you can see in the image, I’ll be using ‘general’ in this example. But this can be anything you want – and you can change it on the fly! (if you want to make different chatrooms or something like that)

Pulling our messages

All righty, now that we are able to push data to our firebase database, we’re ready to pull them back into our app as well. Back in Chatbox.js we’ll complete our componentDidMount() function to pull our firebase data when our chatbox is loaded.

// Chatbox.js
import React from 'react';
import firebase from '../../firebase'; <-- Add this line

class Chatbox extends React.Component{
  ...

  componentDidMount(){
    const chatRef = firebase.database().ref('general');
    chatRef.on('value', snapshot => {
      const getChats = snapshot.val();
      let ascChats = [];
      for(let chat in getChats){
        ascChats.push({
          id: chat,
          message: getChats[chat].message,
          user: getChats[chat].user,
          date: getChats[chat].timestamp
        });
      }
      const chats = ascChats.reverse();
      this.setState({chats});
    });
  }

  render(){
    ...
  }
}
export default Chatbox;

Ok, sooo. On componentDidMount() we get the Chat reference we created previously in our Home component. Make sure to use the same reference as where you’re posting your messages to 😉

That’s actually all there is to do! If we have a chatRef, we take the snapshot and store it in a variable. The snapshot is 1 object, containing all our messages at once.

I want to display the latest messages at the top, so I’m looping over my getChats, and push the data we need into a helperArray called ‘ascChats’. The create a new variable and set it to the reverse version of the regular ascChats array. This way the latest messages are stored at the top. Don’t forget to push the end result to our state! So we can use it in our component.

For the reversing of the array to display the latest messages at the top… I’m pretty sure firebase will have a built-in function for this. But hey… Now we’ve done some javascript array cardio today as well 🙂

Finalize our Chatbox

Now that we have our messages in the state of our Chatbox, we can list them in our chatbox! Yaay.

Update the render method in Chatbox.js like this:

// Chatbox.js
...
render(){
  return(
    <div className="chatbox">
      <ul className='chat-list'>
        {this.state.chats.map(chat => {
          const postDate = new Date(chat.date);
          return(
            <li key={chat.id}>
              <em>{postDate.getDate() + '/' + (postDate.getMonth()+1)}</em>								 
              <strong>{chat.user}:</strong> 								 
              {chat.message}
            </li>
          );
        })}
      </ul>
    </div>
  );
}
...

We loop over the chat messages in our state and display them in our “chat-list”. Pooof! Our chat app is alive, just like that!!

This should do the trick?! A fully functioning React Chat App linked to the Realtime Database in Firebase! Let me know if you got stuck somewhere or if something is not explained correctly. I’ll adjust my tutorial.

As I said before, I switched to using Vuejs instead of React. I’ll ad a link to a Vue-firebase setup as well in the related articles. Or, if you landed on this page and missed part 1 of the React Firebase setup, that one can be found as a related article as well.

Hope you enjoyed it and that my tutorial gave you some more insights!

Peace.

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *