DEV Community

Cover image for MERN Stack for experience developers -Part 2
Jitan Gupta
Jitan Gupta

Posted on

MERN Stack for experience developers -Part 2

If you haven't read first part of this article, read here

In this article we will continue setting up our client-side application.

ReactJS setup

Let's create the React app.
We are again going to use node command line tool for that

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

Note: if create-react-app package is not available, it may prompt you to install it.
image

Bootstrap

We are going to use bootstrap to design the frontend application.
Learn more here.

npm i bootstrap
Enter fullscreen mode Exit fullscreen mode

Let's import the bootstrap design into app.js file and do some basic changes.

import React, { Component } from 'react';
import "bootstrap/dist/css/bootstrap.min.css";
import './App.css';

class App extends Component{
  render(){
    return (
      <div className="container">
        MERN-Stack Todo App
      </div>
    );
  }
}

export default App;
Enter fullscreen mode Exit fullscreen mode

If you will run the application, you can see something like below in your browser.
image

Add routing (React-Router-DOM)

Using react-router-dom we can add routing (multiple pages) into our application.

npm i react-router-dom
Enter fullscreen mode Exit fullscreen mode

Before defining routes, let's create few components first, which we will use to define the pages (routes) in our application. Create create-todo.component.js, edit-todo.component.js and todos-list.component.js into a components folder.
image
After adding the above files, add some sample codes as follows

// creat-todo.component.js
import { Component } from "react";

export default class CreateTodo extends Component{
    render(){
        return(
            <div>Hi From Create todo</div>
        )
    }
}

// edit-todo.component.js
import { Component } from "react";

export default class EditTodo extends Component{
    render(){
        return(
            <div>Hi From Edit todo</div>
        )
    }
}

// todos-list.component.js
import { Component } from "react";

export default class TodosList extends Component {
  render() {
    return <div>Hi From todos list</div>;
  }
}
Enter fullscreen mode Exit fullscreen mode

Let's add the routing into our App.js file now

// importing react & react-router-dom packages
import { Component } from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";

// importing components
import CreateTodo from "./components/create-todo.component";
import TodosList from "./components/todos-list.component";
import EditTodo from "./components/edit-todo.component";

// importing bootstrap & custom css
import "bootstrap/dist/css/bootstrap.min.css";
import "./App.css";


class App extends Component {
  render() {
    return (
      <Router> 
        <nav className="navbar navbar-expand-lg navbar-light bg-light">
          <div className="container">
            <Link to="/" className="navbar-brand">
              MERN Stack Demo
            </Link>
            <div className="collapse navbar-collapse" id="navbarSupportedContent">
              <ul className="navbar-nav me-auto mb-2 mb-lg-0">
                <li className="nav-item">
                  <Link to="/" className="nav-link">
                    Todos
                  </Link>
                </li>
                <li className="nav-item">
                  <Link to="/create" className="nav-link">
                    Add
                  </Link>
                </li>
              </ul>
            </div>
          </div>
        </nav>
        <div className="container">
          <Route path="/" exact component={TodosList} />
          <Route path="/edit/:id" component={EditTodo} />
          <Route path="/create" component={CreateTodo} />
        </div>
      </Router>
    );
  }
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Let's break above code down:
BrowserRouter is a router implementation that uses HTML history API to keep UI in sync with URL.
Link is used to define anchor tags and to is similar to href.
Route: As you can see, it has few properties like path defines the route & component defines which component to use if the path matches. exact tells that the path should be /, not /xyz or /abc.
Note: To read the parameter passed in the URL like /edit/1, we can use this.props.match.params.id. And while defining such routes, we use /:id.

Let's run the application and see what we have so far
image
So far, we have added routes and basic components. It's time to design the list component and bind data from server.

Add Axios

Using axios library we can make HTTP requests such as GET, PUT, POST.

npm i axios
Enter fullscreen mode Exit fullscreen mode

Now we are ready to design our todo list

// importing packages
import { Component } from "react";
import { Link } from "react-router-dom";
import axios from "axios";

// functional component
const Todo = (props) => (    
  <tr>
    <td className={props.todo.completed ? 'completed' : ''}>{props.todo.title}</td>
    <td className={props.todo.completed ? 'completed' : ''}>{props.todo.description}</td>
    <td className={props.todo.completed ? 'completed' : ''}>{props.todo.priority}</td>
    <td>
      <span className={props.todo.completed ? 'd-none' : ''}><button type="button" onClick={() => props.finished(props.todo)} className="btn btn-primary btn-sm mx-1">Finish</button></span>
      <Link className="btn btn-sm btn-secondary" to={"/edit/" + props.todo._id}>Edit</Link>
    </td>
  </tr>
);

// class component
export default class TodosList extends Component {
  constructor(props) {
    super(props);
    // defining the state of the component
    this.state = { todos: [] };

    // this is required for event binding in react
    this.finishTask = this.finishTask.bind(this);
  }  

  // react life cycle method
  componentDidMount() {
    axios
      .get("http://localhost:4000/todos/") // consuming the get api
      .then((res) => {
        this.setState({ todos: res.data }); // data binding
      })
      .catch((error) => {
        console.log(error);
      });
  }

  // finish task event from table, 
  // Please note, we are making a PUT request to server 
  // to update the status of task
  finishTask(task){
    task.completed = true;
    axios
      .put("http://localhost:4000/todos/" + task._id, task)
      .then((res) => {
        if (res.status === 200) { // checking if the response is ok or not, status code 200 is for ok
          this.props.history.push("/"); // refreshing the page, please don't do this in production level application
        }
      });
  }

  // mapping the todo list
  todoList() {
    return this.state.todos.map((todo, index) => {
      return <Todo todo={todo} key={index} finished={(task) => this.finishTask(task)} />;
    });
  }

  // rendering the output
  render() {
    return (
      <div>
       <h4>Todo List</h4>
       <table className="table">
           <thead>
               <tr>
                   <th>Title</th>
                   <th>Description</th>
                   <th>Priority</th>
                   <th></th>
               </tr>
           </thead>
           <tbody>
           { this.todoList() }
           </tbody>
       </table>
      </div>
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

Functional Component is a JavaScript function that returns some JSX. It does not need to extend anything. And to read data in functional component, we use props.
componentDidMount() is a life cycle method form react which run after the component output has been rendered into DOM.

Note:

  1. As you can see, I am consuming APIs in same file, I am doing it to remove additional complications from this demo app.
  2. Don't forget to read the additional comments written in above code file.

Before running your application, let's add some design for our completed task.

.completed {
  text-decoration: line-through;
}
Enter fullscreen mode Exit fullscreen mode

If you will run the application now, you will see something like below
image
Note: As you can see I am already having few todos, I have created these todos using Postman, as shown in previous article.

Now, If will click on finish button, you can see the todo item has been marked as complete.
image

Pro Tip: If you are facing any kind of error and want to debug your code, (a) Open developer tools (ctrl+F12), (b) In source panel search(ctrl+P) for your todo-list.component, (c) add the debugger point on desired line number, and (d) Click on finish button in browser. You will see something like below
image

Moving ahead, let's design the edit page now

// importing packages
import React, { Component } from "react";
import axios from "axios";

// creating edit todo component
export default class EditTodo extends Component {
  constructor(props) {
    super(props);

    // defining state of the data
    this.state = {
      description: "",
      title: "",
      priority: "",
      completed: false,
      message: "",
    };

    // binding click events
    this.handleInputChange = this.handleInputChange.bind(this);
    this.onSubmit = this.onSubmit.bind(this);
  }

  // fetching the data from server
  componentDidMount() {
    axios
      .get("http://localhost:4000/todos/" + this.props.match.params.id)
      .then((response) => {
        // binding the data into current state
        // the bound data will reflect in page
        this.setState({
          description: response.data.description,
          title: response.data.title,
          priority: response.data.priority,
          completed: response.data.completed,
        });
      })
      .catch(function (error) {
        console.log(error);
      });
  }

  // updating the updated value into current state
  // In HTML, 'name' holds the key defined in above state
  // using which, we are updating the data below
  handleInputChange(event) {
    const target = event.target;
    const value = target.type === "checkbox" ? target.checked : target.value;
    this.setState({ [target.name]: value });
  }

  // Updating the data
  onSubmit(e) {
    e.preventDefault();
    const obj = {
      description: this.state.description,
      title: this.state.title,
      priority: this.state.priority,
      completed: this.state.completed,
    };
    axios
      .put("http://localhost:4000/todos/" + this.props.match.params.id, obj)
      .then((res) => {
        if (res.status === 200) {
          this.props.history.push("/"); // after successful update, redirecting to list page
        }
      });
  }

  render() {
    return (
      <div>
        <h3>Update Todo</h3>
        <form onSubmit={this.onSubmit}>
          <div className="form-group">
            <label>title: </label>
            <input
              type="text"
              name="title"
              className="form-control"
              value={this.state.title}
              onChange={this.handleInputChange}
            />
          </div>
          <div className="form-group">
            <label>Description: </label>
            <textarea
              type="text"
              name="description"
              className="form-control"
              rows="3"
              value={this.state.description}
              onChange={this.handleInputChange}
            ></textarea>
          </div>
          <div className="form-group">
            <div className="form-check form-check-inline">
              <input
                className="form-check-input"
                type="radio"
                name="priority"
                id="priorityLow"
                value="Low"
                checked={this.state.priority === "Low"}
                onChange={this.handleInputChange}
              />
              <label className="form-check-label">Low</label>
            </div>
            <div className="form-check form-check-inline">
              <input
                className="form-check-input"
                type="radio"
                name="priority"
                id="priorityMedium"
                value="Medium"
                checked={this.state.priority === "Medium"}
                onChange={this.handleInputChange}
              />
              <label className="form-check-label">Medium</label>
            </div>
            <div className="form-check form-check-inline">
              <input
                className="form-check-input"
                type="radio"
                name="priority"
                id="priorityHigh"
                value="High"
                checked={this.state.priority === "High"}
                onChange={this.handleInputChange}
              />
              <label className="form-check-label">High</label>
            </div>
          </div>
          <div className="form-check">
            <input
              className="form-check-input"
              id="completedCheckbox"
              type="checkbox"
              name="completed"
              onChange={this.handleInputChange}
              checked={this.state.completed}
              value={this.state.completed}
            />
            <label className="form-check-label" htmlFor="completedCheckbox">
              Completed
            </label>
          </div>

          <br />

          <div className="form-group">
            <input
              type="submit"
              value="Update Todo"
              className="btn btn-primary"
            />
          </div>
        </form>
      </div>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Let's break few important attributes of HTML code:

  1. name="priority" key defined in state
  2. value="High" HTML element holding the value.
  3. checked={this.state.priority === "High"} current state of HTML element.
  4. onChange={this.handleInputChange} event to be triggerd on check/uncheck event.

Let's edit some todo now
image
As you can see the data has been updated successfully. And page has been redirect to list again.
Note: I have not added any screen-shot after redirection to make sure you are following along, please confirm by yourself.

Now only create todo task is pending, let's do that now

// importing packages
import React, { Component } from "react";
import axios from "axios";

// create todo component
export default class CreateTodo extends Component {
  constructor(props) {
    super(props);

    this.state = {
      description: "",
      title: "",
      priority: "",
      completed: false,
      message: "",
    };

    this.handleInputChange = this.handleInputChange.bind(this);
    this.onSubmit = this.onSubmit.bind(this);
  }

  handleInputChange(event) {
    const target = event.target;
    const value = target.type === "checkbox" ? target.checked : target.value;
    this.setState({ [target.name]: value });
  }

  onSubmit(e) {
    e.preventDefault();
    const newTodo = {
      description: this.state.description,
      title: this.state.title,
      priority: this.state.priority,
      completed: this.state.completed,
    };
    axios.post("http://localhost:4000/todos/", newTodo).then((res) => {
      // after adding todo successfully, clearing the current state & showing success message
      if (res.status === 200) {
        this.setState({
          description: "",
          title: "",
          priority: "",
          message: "Todo created successfully.", 
        });
      }
    });
  }

  render() {
    return (
      <div style={{ marginTop: 10 }}>
        <h3>Create New Todo</h3>
        <form onSubmit={this.onSubmit}>
          <div className="form-group">
            <label>Title: </label>
            <input
              type="text"
              className="form-control"
              name="title"
              value={this.state.title}
              onChange={this.handleInputChange}
            />
          </div>
          <div className="form-group">
            <label>Description: </label>
            <textarea
              type="text"
              className="form-control"
              name="description"
              rows="3"
              value={this.state.description}
              onChange={this.handleInputChange}
            ></textarea>
          </div>
          <div className="form-group">
            <div className="form-check form-check-inline">
              <label className="form-check-label">
                <input
                  className="form-check-input"
                  type="radio"
                  name="priority"
                  id="priorityLow"
                  value="Low"
                  checked={this.state.priority === "Low"}
                  onChange={this.handleInputChange}
                />
                Low
              </label>
            </div>
            <div className="form-check form-check-inline">
              <label className="form-check-label">
                Medium
                <input
                  className="form-check-input"
                  type="radio"
                  name="priority"
                  id="priorityMedium"
                  value="Medium"
                  checked={this.state.priority === "Medium"}
                  onChange={this.handleInputChange}
                />
              </label>
            </div>
            <div className="form-check form-check-inline">
              <label className="form-check-label">
                <input
                  className="form-check-input"
                  type="radio"
                  name="priority"
                  id="priorityHigh"
                  value="High"
                  checked={this.state.priority === "High"}
                  onChange={this.handleInputChange}
                />
                High
              </label>
            </div>
          </div>
          <p>{this.state.message}</p>
          <div className="form-group">
            <input
              type="submit"
              value="Create Todo"
              className="btn btn-primary"
            />
          </div>
        </form>
      </div>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Let's run the application and create some todos, click on add from navbar & you will see a page like below, add some information & click on create todo
image
image
Finally, we have completed the code for our todo application.
yay

Few points before wrapping up this article...

Error

Cannot read property of undefined, after clicking finish todo button
image
This error is occurring because this context is not available in current scope. To resolve this change the function to arrow function. In our demo above, array function has been used.

return this.state.todos.map((todo, index) =>{
      return <Todo todo={todo} key={index} finished={(task) => this.finishTask(task)} />;
    });
Enter fullscreen mode Exit fullscreen mode

below code breaks

return this.state.todos.map(function(todo, index) { // this breaks
      return <Todo todo={todo} key={index} finished={(task) => this.finishTask(task)} />;
    });
Enter fullscreen mode Exit fullscreen mode

Points to keep in mind

  1. Source code of the article can be found here
  2. For debugging your node app, follow YouTube video
  3. You may have noticed that I have used a single repository to manage both of my applications. It is just for this demo. Here is my workspace, and how I am running both applications parallelly.image

Top comments (0)