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
Note: if create-react-app package is not available, it may prompt you to install it.
Bootstrap
We are going to use bootstrap to design the frontend application.
Learn more here.
npm i bootstrap
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;
If you will run the application, you can see something like below in your browser.
Add routing (React-Router-DOM)
Using react-router-dom
we can add routing (multiple pages) into our application.
npm i react-router-dom
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.
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>;
}
}
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;
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
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
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>
);
}
}
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:
- As you can see, I am consuming APIs in same file, I am doing it to remove additional complications from this demo app.
- 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;
}
If you will run the application now, you will see something like below
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.
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
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>
);
}
}
Let's break few important attributes of HTML code:
-
name="priority"
key defined in state -
value="High"
HTML element holding the value. -
checked={this.state.priority === "High"}
current state of HTML element. -
onChange={this.handleInputChange}
event to be triggerd oncheck
/uncheck
event.
Let's edit some todo now
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>
);
}
}
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
Finally, we have completed the code for our todo application.
Few points before wrapping up this article...
Error
Cannot read property of undefined, after clicking finish todo button
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)} />;
});
below code breaks
return this.state.todos.map(function(todo, index) { // this breaks
return <Todo todo={todo} key={index} finished={(task) => this.finishTask(task)} />;
});
Top comments (0)