DEV Community

Cover image for How to create a simple and beautiful chat with MongoDB, Express, React and Node.js (MERN stack)
Armel
Armel

Posted on • Updated on

How to create a simple and beautiful chat with MongoDB, Express, React and Node.js (MERN stack)

Recently, I worked on an interesting project called SpeedBoard which is a real-time board for Agile and Scrum retrospectives. It's the kind of tool we use at work after our Scrum Sprint review to easily share our feedback about the last Sprint.

Since it was a very enriching experience, I thought that I would do a quick tutorial on how to set up a simple chat with the same technology stack which includes: MongoDB, Express, React, Node.js and is also called the MERN stack. I am also using Socket.IO for the real-time engine and Material-UI which is a UI framework for React based on Material Design.

If you don't want to wait until the end of this tutorial, you can already check a preview of the final result, and also check the Github repository if you want to fork it and start to improve it ;)

Prerequisites

In this tutorial, we will use Heroku for hosting our live project and Github for hosting our code and deploying it to Heroku, so make sure you already have an account with them, they both provide a free sign up.

Structure

Before we start, let's have a quick look at the structure of our project. Inside our root folder, we will have 2 subfolders: one called client which contains the React app and one called server with our Node.js server:

speedchatapp/
├── client/
├── server/
Enter fullscreen mode Exit fullscreen mode

Let's open our Terminal and create our project folder:

mkdir speedchatapp
cd speedchatapp/
Enter fullscreen mode Exit fullscreen mode

Set up the client

On the client-side, we will use the Create React App (CRA) which provides a very easy way to start building any React SPA.

CRA provides a very simple command to install the app, but first, let's ensure that npx is using the latest version if you used create-react-app in the past:

npm uninstall -g create-react-app
Enter fullscreen mode Exit fullscreen mode

Now, let's create our app in our client folder with this simple command:

npx create-react-app client
Enter fullscreen mode Exit fullscreen mode

This might take a couple of minutes to install all the dependencies, and once you are done, try:

cd client/
npm start
Enter fullscreen mode Exit fullscreen mode

You should now be able to access your app at http://localhost:3000/

Create React App

That was quick and simple : ) But still pretty far from our final result! We'll come back a little bit later to our React app once the server-side of our project is ready.

Set up the server

Now that we have the skeleton of our client ready, let's have a look at the backend side.

First, let's create our server folder at the root of our project and initialize our package.json file:

mkdir server
cd server/
npm init
Enter fullscreen mode Exit fullscreen mode

A utility will take you through the configuration of the file but you can type Enter for all options for this tutorial.

Now, we will install all the dependencies required for our server (Express, Mongoose and Socket.IO) with the following command:

npm install express mongoose socket.io --save
Enter fullscreen mode Exit fullscreen mode

Then, copy the .gitignore file from the client folder to the server folder to prevent some files and folders to be pushed to our GitHub repository (e.g. /node_modules folder):

cp ../client/.gitignore ./
Enter fullscreen mode Exit fullscreen mode

We will create the 2 files necessary for our server to work. The first one (Message.js) is the schema of the documents we will keep in our database. We will need 3 information: the name of the user who is posting a message in the chat, the content of its message and a timestamp to know when he posted his message.

server/Message.js

const mongoose = require('mongoose');

const messageSchema = new mongoose.Schema({
  content: String,
  name: String,
}, {
  timestamps: true,
});

module.exports = mongoose.model('Message', messageSchema);
Enter fullscreen mode Exit fullscreen mode

The second one (index.js) is our main file, I won't go too much into details because that would make this tutorial a bit too long, but feel free to ask any question in the comments, I'll be glad to answer them or improve the comments directly in the code if necessary.

server/index.js

const express = require('express');
const app = express();
const http = require('http').Server(app);
const path = require('path');
const io = require('socket.io')(http);

const uri = process.env.MONGODB_URI;
const port = process.env.PORT || 5000;

const Message = require('./Message');
const mongoose = require('mongoose');

mongoose.connect(uri, {
  useUnifiedTopology: true,
  useNewUrlParser: true,
});

app.use(express.static(path.join(__dirname, '..', 'client', 'build')));

io.on('connection', (socket) => {

  // Get the last 10 messages from the database.
  Message.find().sort({createdAt: -1}).limit(10).exec((err, messages) => {
    if (err) return console.error(err);

    // Send the last messages to the user.
    socket.emit('init', messages);
  });

  // Listen to connected users for a new message.
  socket.on('message', (msg) => {
    // Create a message with the content and the name of the user.
    const message = new Message({
      content: msg.content,
      name: msg.name,
    });

    // Save the message to the database.
    message.save((err) => {
      if (err) return console.error(err);
    });

    // Notify all other users about a new message.
    socket.broadcast.emit('push', msg);
  });
});

http.listen(port, () => {
  console.log('listening on *:' + port);
});
Enter fullscreen mode Exit fullscreen mode

The structure of your project should now look like this:

speedchatapp/
├── client/
│   └── (Several files and folders)
└── server/
    ├── node_modules/
    ├── .gitignore
    ├── index.js
    ├── Message.js
    ├── package-lock.json (auto-generated)
    └── package.json
Enter fullscreen mode Exit fullscreen mode

Before coming back to our React app to finish our project, let's set up our Heroku hosting and link it to our Github repository to make sure the deployment works fine.

Set up our Heroku hosting

Let's download and install the Heroku CLI to set up everything from our Terminal.

Once downloaded and installed, let's go back to our Terminal and login to our Heroku account:

heroku login
Enter fullscreen mode Exit fullscreen mode

It will open a new tab in your browser and once you are logged in, you can close the browser tab and go back to your Terminal.

Now let's create our new app that will host our project:

heroku create
Enter fullscreen mode Exit fullscreen mode

It will automatically generate an identifier with a URL where you can access your app, it should look like this:

https://sleepy-meadow-81798.herokuapp.com/

Heroku App

You can rename your app if you want something a little bit easier to remember, you can then use it for the rest of this tutorial:

Rename the app in Heroku UI

Alright, now we need our MongoDB database to store the chat messages from the users. Let's add the mongolab addon to our app:

heroku addons:create mongolab --app speedchatapp
Enter fullscreen mode Exit fullscreen mode

I used speedchatapp in the previous command because I renamed my application but you should use the one provided when you created it if you didn't rename it, for example, sleepy-meadow-81798.

Once created it will show you the name of a variable in green, i.e MONGODB_URI. Now let's get the configuration URI of our newly created database:

heroku config:get MONGODB_URI
Enter fullscreen mode Exit fullscreen mode

You should see something like this:

mongodb://heroku_123abc:abc123@ds141188.mlab.com:41188/heroku_123abc
Enter fullscreen mode Exit fullscreen mode

Copy this URI, and create a file at the root of your project called .env with the following content [VARIABLE_IN_GREEN]=[URI]. It should look like this:

MONGODB_URI=mongodb://heroku_123abc:abc123@ds141188.mlab.com:41188/heroku_123abc
Enter fullscreen mode Exit fullscreen mode

Let's copy one more time the .gitignore and add the .env file at the end of it to avoid pushing the credentials of our database to GitHub:

cp server/.gitignore ./
echo '.env' >> .gitignore
Enter fullscreen mode Exit fullscreen mode

During the deployment of our app, we need to tell Heroku how to start our server. It can be done by using a Procfile that we will put at the root of our project. So let's create it and add the command line that will start our server:

echo 'web: node server/index.js' > Procfile
Enter fullscreen mode Exit fullscreen mode

Now let's initialize another package.json at the root of our project. Same as before, don't worry about all the options, for now, just type Enter at all prompts:

npm init
Enter fullscreen mode Exit fullscreen mode

One last thing we want to do here is to install the npm package called Concurrently that will allow us to run both the server and the client in a single command line during our development mode:

npm install --save-dev concurrently
Enter fullscreen mode Exit fullscreen mode

And finally, in our newly created package.json at the root of the project, we will add 2 lines in the scripts section:

"scripts": {
    "dev": "concurrently --kill-others \"heroku local\" \"npm run start --prefix ./client\"",
    "postinstall": "npm install --prefix ./server && npm install --prefix ./client && npm run build --prefix ./client",
}
Enter fullscreen mode Exit fullscreen mode

The postinstall command, as you can guess, will be executed after Heroku has finished running the npm install command at the root of our folder. It's telling Heroku to also run the npm install command inside our client and server folder and will also build our React app for production.

Now, it's time to test it, go to the root of your project and type:

npm run dev
Enter fullscreen mode Exit fullscreen mode

This will launch the server and our React app in development mode, and it should open a window in your browser with the previous landing page of our React app.

In your terminal, you should see something like this:

> concurrently --kill-others "heroku local" "npm run start --prefix ./client"

[1] 
[1] > react-scripts start
[1] 
[0] [OKAY] Loaded ENV .env File as KEY=VALUE Format
[0] 12:16:15 PM web.1 |  listening on *:5000
[1] Starting the development server...
[1] 
[1] Compiled successfully!
[1] 
[1] You can now view client in the browser.
[1] 
[1]   Local:            http://localhost:3000/
[1]   On Your Network:  http://192.168.0.10:3000/
[1] 
[1] Note that the development build is not optimized.
[1] To create a production build, use npm run build.
Enter fullscreen mode Exit fullscreen mode

Note: we are using the same database for both Dev and Live mode, if you want to use a different database, you can always create another one in Heroku like we have seen before and update your .env file with the credentials of your new database to make sure it won't interfere with the one in production.

Set up GitHub and link to Heroku

Now, we are going create a new repository on GitHub, and we are going to connect it to Heroku so every time we will merge a Pull Request on the master branch, it will automatically deploy it on Heroku.

Let's create our repository on GitHub. Go to https://github.com/new:

Create a new repository on Github

Write down the repository URL that we will use in the next step. Back to our Terminal, in the root folder of our project:

// Initialize the root folder as a Git repository
git init 

// Add all the files for the initial commit
git add .

// Commit staged files
git commit -m "Initial commit"

// Set the GitHub remote repository
git remote add origin <repository url>

// Push the local changes to GitHub
git push origin master
Enter fullscreen mode Exit fullscreen mode

Now our code is on GitHub, let's link this repository to our Heroku app.

From the Heroku UI, select your app and click on the Deploy tab. In the Deployment method, click on Github, type your repository name and click on Connect:

Connect to GitHub

Also, make sure that the "Enable Automatic Deploys" on the master branch is activated:

Enable Automatic Deploys

It should now look like this:

Automatic Deploys Enabled

Now let's trigger a first manual deployment to check that everything is fine. Click on the Deploy Branch and wait until you see you see Your app was successfully deployed.

Finally, after clicking on the Open App button at the top right of the page, you should see the React app on your Heroku hosting.

From now on, after pushing any update to your GitHub repository, you should see the deployment triggered automatically in your Heroku UI:

Deployment triggered

Finishing the client

Now that the architecture of our project is ready, let's finish our clientReact app.

The first thing we will need here is to install our frontend dependencies in the client folder: Socket.IO for client, Material-UI core and icons:

cd client/
npm install socket.io-client @material-ui/core @material-ui/icons --save
Enter fullscreen mode Exit fullscreen mode

Now in the client/package.json, add the following proxy field at the end of the file:

"proxy": "http://localhost:5000"
Enter fullscreen mode Exit fullscreen mode

It will tell the development server to proxy any unknown requests to your server in development. Check the official documentation for more information.

Next, we'll create a config.js file to tell our app to switch endpoints in case we are on our local machine or live hosting:

client/src/config.js

import pkg from '../package.json';

export default {
  development: {
    endpoint: pkg.proxy
  },
  production: {
    endpoint: window.location.hostname
  }
}
Enter fullscreen mode Exit fullscreen mode

Okay now let's start our local development environment from our root folder:

npm run dev
Enter fullscreen mode Exit fullscreen mode

Last steps

For the last step, either create or update each file below manually or go directly to the GitHub repository to check out the project.

Replace client/src/App.css:

body {
  background: #f5f5f5;
  padding: 16px;
}

#chat {
  max-height: calc(100vh - 128px);
  overflow: scroll;
  padding: 16px;
}

.name {
  color: rgba(0, 0, 0, 0.54);
}

.content {
  margin-bottom: 8px;
}
Enter fullscreen mode Exit fullscreen mode

Replace client/src/App.js:

import React from 'react';
import config from './config';
import io from 'socket.io-client';

import Paper from '@material-ui/core/Paper';
import Typography from '@material-ui/core/Typography';

import BottomBar from './BottomBar';
import './App.css';

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      chat: [],
      content: '',
      name: '',
    };
  }

  componentDidMount() {
    this.socket = io(config[process.env.NODE_ENV].endpoint);

    // Load the last 10 messages in the window.
    this.socket.on('init', (msg) => {
      let msgReversed = msg.reverse();
      this.setState((state) => ({
        chat: [...state.chat, ...msgReversed],
      }), this.scrollToBottom);
    });

    // Update the chat if a new message is broadcasted.
    this.socket.on('push', (msg) => {
      this.setState((state) => ({
        chat: [...state.chat, msg],
      }), this.scrollToBottom);
    });
  }

  // Save the message the user is typing in the input field.
  handleContent(event) {
    this.setState({
      content: event.target.value,
    });
  }

  //
  handleName(event) {
    this.setState({
      name: event.target.value,
    });
  }

  handleSubmit(event) {
    // Prevent the form to reload the current page.
    event.preventDefault();

    // Send the new message to the server.
    this.socket.emit('message', {
      name: this.state.name,
      content: this.state.content,
    });

    this.setState((state) => {
      // Update the chat with the user's message and remove the current message.
      return {
        chat: [...state.chat, {
          name: state.name,
          content: state.content,
        }],
        content: '',
      };
    }, this.scrollToBottom);
  }

  // Always make sure the window is scrolled down to the last message.
  scrollToBottom() {
    const chat = document.getElementById('chat');
    chat.scrollTop = chat.scrollHeight;
  }

  render() {
    return (
      <div className="App">
        <Paper id="chat" elevation={3}>
          {this.state.chat.map((el, index) => {
            return (
              <div key={index}>
                <Typography variant="caption" className="name">
                  {el.name}
                </Typography>
                <Typography variant="body1" className="content">
                  {el.content}
                </Typography>
              </div>
            );
          })}
        </Paper>
        <BottomBar
          content={this.state.content}
          handleContent={this.handleContent.bind(this)}
          handleName={this.handleName.bind(this)}
          handleSubmit={this.handleSubmit.bind(this)}
          name={this.state.name}
        />
      </div>
    );
  }
};

export default App;
Enter fullscreen mode Exit fullscreen mode

Create client/src/BottomBar.js:

import React from 'react';

import { fade, makeStyles } from '@material-ui/core/styles';
import AppBar from '@material-ui/core/AppBar';
import InputBase from '@material-ui/core/InputBase';
import Toolbar from '@material-ui/core/Toolbar';

import ChatIcon from '@material-ui/icons/Chat';
import FaceIcon from '@material-ui/icons/Face';

const useStyles = makeStyles(theme => ({
  appBar: {
    bottom: 0,
    top: 'auto',
  },
  inputContainer: {
    backgroundColor: fade(theme.palette.common.white, 0.15),
    '&:hover': {
      backgroundColor: fade(theme.palette.common.white, 0.25),
    },
    borderRadius: theme.shape.borderRadius,
    marginLeft: theme.spacing(1),
    position: 'relative',
    width: '100%',
  },
  icon: {
    width: theme.spacing(7),
    height: '100%',
    position: 'absolute',
    pointerEvents: 'none',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
  },
  inputRoot: {
    color: 'inherit',
  },
  inputInput: {
    padding: theme.spacing(1, 1, 1, 7),
    width: '100%',
  },
}));

export default function BottomBar(props) {
  const classes = useStyles();

  return (
    <AppBar position="fixed" className={classes.appBar}>
      <Toolbar>
        <div className={classes.inputContainer} style={{maxWidth: '200px'}}>
          <div className={classes.icon}>
            <FaceIcon />
          </div>
          <InputBase
            onChange={props.handleName}
            value={props.name}
            placeholder="Name"
            classes={{
              root: classes.inputRoot,
              input: classes.inputInput,
            }}
            inputProps={{ 'aria-label': 'name' }}
          />
        </div>
        <div className={classes.inputContainer}>
          <form onSubmit={props.handleSubmit}>
            <div className={classes.icon}>
              <ChatIcon />
            </div>
            <InputBase
              onChange={props.handleContent}
              value={props.content}
              placeholder="Type your message..."
              classes={{
                root: classes.inputRoot,
                input: classes.inputInput,
              }}
              inputProps={{ 'aria-label': 'content' }}
            />
          </form>
        </div>
      </Toolbar>
    </AppBar>
  );
}
Enter fullscreen mode Exit fullscreen mode

Every time you update the code, you should see the project at http://localhost:3000 automatically reload with the last changes.

Finally, let's push our latest update to GitHub to trigger a new deployment on our live project:

git add .
git commit -m "Final update"
git push origin master
Enter fullscreen mode Exit fullscreen mode

Et voilà, Bob's your uncle! Our chat is now finished and ready: https://speedchatapp.herokuapp.com/

If you have any question, feel free to ask in the comments, I'll be glad to answer it and improve this tutorial. And feel free to fork the project to improve it ;)

Top comments (32)

Collapse
 
syedabrar003 profile image
syedabrar003

I'm unable to deploy the master branch. I'm getting an error in the build.
npm ERR! code ENOENT
npm ERR! syscall open
npm ERR! path /tmp/build_4120397c_/client/package.json
npm ERR! errno -2
npm ERR! enoent ENOENT: no such file or directory, open '/tmp/build_4120397c_/client/package.json'
npm ERR! enoent This is related to npm not being able to find a file.
npm ERR! enoent

   npm ERR! A complete log of this run can be found in:
   npm ERR!     /tmp/npmcache.VwQor/_logs/2020-08-04T20_56_07_801Z-debug.log
   npm ERR! code ELIFECYCLE
   npm ERR! errno 254
   npm ERR! chatfinal@1.0.0 postinstall: `npm install --prefix ./server && npm install --prefix ./client && npm run build --prefix ./client`
   npm ERR! Exit status 254
   npm ERR! 
   npm ERR! Failed at the chatfinal@1.0.0 postinstall script.
   npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

   npm ERR! A complete log of this run can be found in:
   npm ERR!     /tmp/npmcache.VwQor/_logs/2020-08-04T20_56_07_826Z-debug.log
Enter fullscreen mode Exit fullscreen mode

-----> Build failed

   We're sorry this build is failing! You can troubleshoot common issues here:
   https://devcenter.heroku.com/articles/troubleshooting-node-deploys

   Some possible problems:

   - Node version not specified in package.json
     https://devcenter.heroku.com/articles/nodejs-support#specifying-a-node-js-version

   Love,
   Heroku
Enter fullscreen mode Exit fullscreen mode

! Push rejected, failed to compile Node.js app.
! Push failed

Collapse
 
prachita11 profile image
Prachita Nayak

Hi , I faced the same issue , but in my case there was a .git folder present in my client folder which was preventing the deploy . just delete that .git folder and reinitialise your repository and the build will automatically start on heroku.

Collapse
 
nicodeio profile image
Nicolas Denoël • Edited

Hello, I have the same kind of issue first the same as syedabra003 but I added the node version to my package.json and then the error switched now to this one. I'm at the early step of the guide, when trying manual deploy with Heroku, I started from scratch two times but still the same result, 'npm run dev' runs fine on local so if any clues ? Thanks !

Collapse
 
armelpingault profile image
Armel

Hi, how do you reproduce the error exactly? Which command do you try to execute? Thanks

Collapse
 
syedabrar003 profile image
syedabrar003

Hey,
I followed every step in the post. This output is occurred when I entered 'npm run dev' in terminal.

Collapse
 
tonas_5 profile image
Drybone

In case anybody else runs into the same issue, I think the client's package "name" needs to be "client", in case you copy in your own react app... Also, Procfile should not have ' ' around the 'web: ... ' part. Not 100% if that's what solved it for me, but oh well.

Collapse
 
armelpingault profile image
Armel • Edited

Hi Drybone,

Yes, the Procfile should not include the single quote, I am using macOS and the command I wrote in the article is not adding the quote. Maybe it does under another OS? Anyway, the file should be like this:
github.com/armelpingault/speedchat...

And what do you mean exactly by the client's package "name" needs to be "client"?

I am not sure I understand :)

Collapse
 
prachita11 profile image
Prachita Nayak

yes removing the single quotes solved this issue for me.

Collapse
 
jatinranka profile image
JatinRanka

In server/Message.js file:

const messageSchema = new mongoose.Schema({
content: String,
name: String,
}, {
timestamps: true, -----> this line
});

Is timestamp true by default?

I tried _id.getTimestamp() on a MongoDB _id in which "timestamp: true" is not passed as a parameter, but is still returning the timestamp.

So, is it truly necessary or does it have any other use other than storing the time of creation?

Collapse
 
iamharsh profile image
Harsh Verma

timestamp : true, creates a createdAt and updatedAt field while your inserting documents in the document itself, by default it is not added.

The latter getTimestamp() is a function which finds created date. So there is a difference.

Collapse
 
jatinranka profile image
JatinRanka

I think you interpreted my doubt in the wrong way.

I didn't pass "timestamps: true" as a parameter. And still, it returned the createdAt field when I tried "_id.getTimestamp()". Then what is the use of passing "timestamps: true" as a parameter?

Thread Thread
 
iamharsh profile image
Harsh Verma • Edited

Lets take a sample model for a signup in mongoose -->
var userSchema = new Schema({
email : String,
password : String,
fullName : String,
userName : {
type : String,
unique : true
}
})

This piece of code will create a mongodb document of this format -->
{
"id" : ObjectId("5eac7f0101dce40f15a97e8d"),
"email" : "asd@asd.com",
"userName" : "hv98",
"fullName" : "asd",
"password" : "asd",
"
_v" : 0
}

Notice this doesn't have the createdAt and updatedAt fields

Now a sample model with the timestamp true field -->

var imageSchema = new Schema({
username : String,
description : String,
imagePath : {
type : String
},
comments : [commentSchema],
likes : [String],
nsfw : {
type : Boolean,
default : false
}

},{
timestamps : true
})

A document from this model would look like this -->
"id" : ObjectId("5eb02f999a15002d41f83e14"),
"likes" : [
"hv98"
],
"nsfw" : false,
"username" : "hv98",
"description" : "d",
"imagePath" : "1588604825052IMG_3265.JPG",
"comments" : [
{
"_id" : ObjectId("5eb1581ff810f83199fca925"),
"username" : "hv98",
"comment" : "dd",
"updatedAt" : ISODate("2020-05-05T12:12:15.736Z"),
"createdAt" : ISODate("2020-05-05T12:12:15.736Z")
}
],
"createdAt" : ISODate("2020-05-04T15:07:05.068Z"),
"updatedAt" : ISODate("2020-05-05T12:20:37.408Z"),
"
_v" : 0
}

Now if you notice this document has a field called createdAt and updatedAt which was not the case in the earlier one

So when you use _id.getTimestamp() you get the timestamp but it is not a field which is already present in the document but something which the function does and if you have the timestamp : true then this is a field in the document and doesn't require an extra function to be called.

I hope this can settle the difference.

Edit -- **
**One of the uses of the createdAt field is displaying the documents in ascending or descending order.

eg code -->
Image.find({}).sort({ createdAt: -1 }).exec(function(err, docs) {
if(err) console.log(err);
res.json(docs);
});

This returns all the documents and sort them in ascending order that is the latest doc is displayed first and sends it to your client.

Thread Thread
 
jatinranka profile image
JatinRanka • Edited

Amazing explanation Harsh. This cleared all my doubts.

Collapse
 
armelpingault profile image
Armel

Thanks for the reply Harsh ;)

Collapse
 
jeffsoriano profile image
Jeff Soriano

Can you please explain why you use socket.broadcast.emit for your 'push' event? It seems like socket.emit would work just fine but it doesn't. I've read this cheat sheet and it doesn't seem to explain why it wouldn't work:

socket.io/docs/v3/emit-cheatsheet/...

Collapse
 
armelpingault profile image
Armel

Hi Jeff, you might be right, I didn't test it with socket.emit, but it could be a mistake on my side ;)

Collapse
 
jeffsoriano profile image
Jeff Soriano • Edited

Thanks for the guide. I needed to install 'dotenv' and add require('dotenv').config(); to the top of index.js so that I could run it locally. I also needed to add this since I was using socket.io v3.0+:

const io = require('socket.io')(http, {
cors: {
origin: "localhost:3000",
methods: ["GET", "POST"]
}
});

To avoid the CORS errors

socket.io/docs/v3/migrating-from-2...

Collapse
 
yodist profile image
Yodi Satriani

Hi I have an issue to share with you guys. I got an issue if I wrap the with . You will have your code inside setState run twice. The possible way to fix this is to move the logic outside of setState. I have fix this two setState in App.js.

in componentDidMount -> I moved the msg.reverse() outside setState

componentDidMount() {
  this.socket = io(config[process.env.NODE_ENV].endpoint);

// Load the last 10 messages in the window.
this.socket.on('init', (msg) => {
  let msgReversed = msg.reverse();
  this.setState((state) => ({
    chat: [...state.chat, ...msgReversed],
  }), this.scrollToBottom);
});

// Update the chat if a new message is broadcasted.
  this.socket.on('push', (msg) => {
    this.setState((state) => ({
      chat: [...state.chat, msg],
    }), this.scrollToBottom);
  });
}

and in handleSubmit -> I moved the this.socket.emit function call outside. preventing from emit the message twice

handleSubmit(event) {
  // Prevent the form to reload the current page.
  event.preventDefault();

  // Send the new message to the server.
  this.socket.emit('message', {
    name: this.state.name,
    content: this.state.content,
  });

  this.setState((state) => {
    // Update the chat with the user's message and remove the current message.
    return {
      chat: [...state.chat, {
        name: state.name,
        content: state.content,
      }],
      content: '',
    };
  }, this.scrollToBottom);
}

Hope it can help. Thank you Armel.

Collapse
 
armelpingault profile image
Armel • Edited

Hi Yodi, thanks a lot, I have updated the source code on Github and in the article ;)

Collapse
 
sumnanazadi profile image
Sumnan Azadi

I tried this on my localhost but here I got
"GET http://localhost:5000/socket.io/?EIO=4&transport=polling&t=NMdKy4- net::ERR_FAILED"
"Access to XMLHttpRequest at 'http://localhost:5000/socket.io/?EIO=4&transport=polling&t=NMdKy4-' from origin 'localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource."

Collapse
 
jeffsoriano profile image
Jeff Soriano

If you're using socket.io v3.0+ you need to add this to index.js in the server (replace old code with this):

const io = require('socket.io')(http, {
cors: {
origin: "localhost:3000",
methods: ["GET", "POST"]
}
});

socket.io/docs/v3/migrating-from-2...

Collapse
 
ananyaguptaa profile image
ananyaguptaa • Edited

How to create a simple and beautiful chat with MongoDB, Express, React and Node.js (MERN stack) is an interesting topic. Get some important points which I didn't know before. Thanks for sharing it. If you want to know about the MERN Stack course then visit: cetpainfotech.com/technolgy/mern-s...

Collapse
 
prachita11 profile image
Prachita Nayak

hey , mlab is discontinued and im completely new to heroku . which addon should be used now?

Collapse
 
armelpingault profile image
Armel

Hi Prachita, you can find a free development solution on mongodb.com/, this is what I am using right now ;)

Collapse
 
ananyaguptaa profile image
ananyaguptaa

After using that package.json file will be created I heard but I am unable to create if you an idea or suggestion where I could do a mistake

{
“name”: “test”,
“version”: “1.0.0”,
“description”: “”,
“main”: “index.js”,
“scripts”: {
“test”: “echo \”Error: no test specified\” && exit 1"
},
“author”: “”,
“license”: “ISC”
}
CETPA- app directory is now set

Some comments may only be visible to logged-in visitors. Sign in to view all comments.