React JS Blog CRUD operation

In this tutorial, we look at how to implement simple blog with Add, Edit, Delete and Listing Posts in React JS. As an example, we will use sample JSON data from jsonplaceholder.com.
Demo

First, head over to nodejs.org and install Node if you already don't have it on your machine. Now, open up the Terminal on Mac or Command Prompt on Windows and run the following command to install create-react-app package.

npm install -g create-react-app

Create Your First React App

Now, type the following command to create a new react app:

create-react-app example2

Now, go to the project folder:

cd example2

Install Dependencies for this Project

npm install @material-ui/core
npm install axios
npm install moment
npm i --save react-render-html
npm install --save react-router-dom
npm install --save sweetalert2

The "package.json" will get update and may look like as below (**Don't update manually**)

package.json

Example

{
  "name": "example3",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@material-ui/core": "^3.9.2",
    "axios": "^0.18.0",
    "moment": "^2.24.0",
    "react": "^16.8.5",
    "react-dom": "^16.8.5",
    "react-render-html": "^0.6.0",
    "react-router-dom": "^5.0.0",
    "react-scripts": "2.1.8",
    "sweetalert2": "^8.5.0"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": [
    ">0.2%",
    "not dead",
    "not ie <= 11",
    "not op_mini all"
  ]
}

Type out the below code into Header.js

src\components\Layout\Header.js

Example

import React from 'react';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
 
export default () => {
  return (
    <AppBar position="static">
      <Toolbar>
        <Typography variant="headline" color="colorSecondary" noWrap>
          Simple Blog Example
        </Typography>
      </Toolbar>
    </AppBar>
  );
};

Create top menu

src\components\Layout\Navigation.js

Example

import React from 'react';
import {Link} from 'react-router-dom';
import './Navigation.css';
 
const Navigation = () => {
    return (
        <nav className="col-md-2">
        	<ul>
				<li><div className="sidebar_item"><Link to={'/'}>List All Posts</Link></div></li>
            	<li><div className="sidebar_item" ><Link to={'/create'}>Add New Post</Link></div></li>
            </ul>
        </nav>
     );
}
 
export default Navigation;

src\components\Layout\Layout.js

Example

import Header from './Header';
import Navigation from './Navigation';
 
export {Header , Navigation}

Top menu style

src\components\Layout\Navigation.css

Example

.sidebar_item {    
    display: block;
   	width: 50px;
   	height: 50px;   	
   	margin-top: 20px;   
}
 
nav ul  {
	position: relative;
	display: block;
}
 
h1{
	 padding-left: 300px;
	 text-decoration: none;
	 color: white;
}

Router to fetch JSON data from API

src\components\Router.js

Example

import React, { Component } from 'react';
 
import {BrowserRouter, Route, Switch , Redirect} from 'react-router-dom';
import axios from 'axios';
import Swal from 'sweetalert2';
 
import {Header, Navigation} from './Layout/Layout';
import Posts from './Posts';
import SinglePost from './SinglePost';
import Form from './Form';
import EditPost from './EditPost';
 
class Router extends Component {
    state = {  
        posts: []
    }
 
    componentDidMount() {
        this.getPost();
    }
 
    getPost = () => {
        axios.get(`https://jsonplaceholder.typicode.com/posts`)
             .then( res => {
                 this.setState({
                     posts: res.data
                 }) 
             })
    }
 
    deletePost = (id) => {
        //console.log(id);
        axios.delete(`https://jsonplaceholder.typicode.com/posts/${id}`)
        .then(res => {
            if (res.status === 200) {
                const posts = [...this.state.posts];
                let result = posts.filter(post => (
                    post.id !== id
                ));
                this.setState({
                    posts: result
                })
            } 
        })
    }
 
    createPost = (post) => {
        axios.post(`https://jsonplaceholder.typicode.com/posts`, {post})
             .then(res => {
                 if (res.status === 201) {
                    Swal.fire(
                        'Post Create',
                        'It is created correctly.',
                        'success'
                    )
 
                    let postId = {id: res.data.id};
                    const newPost = Object.assign({}, res.data.post, postId)
 
                    this.setState(prevState => ({
                        posts: [...prevState.posts, newPost]
                    }))
                 }
             })
    }
 
    editPost = (postUpdate) => {
        const {id} = postUpdate;
 
        axios.put(`https://jsonplaceholder.typicode.com/posts/${id}`, {postUpdate})
             .then(res => {
                 if (res.status === 200) {
                    Swal.fire(
                        'Post Updated',
                        'The changes were saved correctly.',
                        'success'
                    )
 
                    let postId = res.data.id;
 
					const posts = [...this.state.posts];
 
                    const postEdit = posts.findIndex(post => postId === post.id)
 
                    posts[postEdit] = postUpdate;
                    this.setState({
                        posts 
                    })
                 }
             })
    }
 
    render() { 
        return (  
            <BrowserRouter>
 
                <div className="container">
                    <Header />
                    <div className="row justify-content-center">
 
                        <Navigation />
 
                        <Switch>
                            <Route exact path="/" render={ () => {
                                return(
                                    <Posts 
                                        posts={this.state.posts}
                                        deletePost={this.deletePost}
                                    />
                                );
                            }} />
 
                            <Route exact path="/post/:postId" render={ (props) => {
                                let idPost = props.location.pathname.replace('/post/', '')
 
                                const posts=this.state.posts;
                                let filter;
                                filter = posts.filter(post => (
                                    post.id === Number(idPost)
                                ))
 
 
                                return(
                                    <SinglePost 
                                        post={filter[0]} 
                                    />
                                )
                            }} />
                            <Route exact path="/create" render={() => {
                                return(
                                    <Form 
                                        createPost={this.createPost}
                                    />
                                );
                            }}
                            />
                            <Route exact path="/edit/:postId" render={ (props) => {
                                let idPost = props.location.pathname.replace('/edit/', '')
                                const posts=this.state.posts;
                                let filter;
                                filter = posts.filter(post => (
                                    post.id === Number(idPost)
                                ))                                
                                return(
                                    <EditPost
                                        post={filter[0]} 
                                        editPost={this.editPost}
                                    />
                                )
                            }} />                            
                        </Switch>
                    </div>
                </div>            
            </BrowserRouter>
        );
    }
} 
export default Router;

Design form controls to Add New Post

src\components\Form.js

Example

import React, { Component } from 'react';
 
class Form extends Component {
    //create refs
    authorRef = React.createRef();
    titleRef = React.createRef();
    contentRef = React.createRef();
    categoryRef = React.createRef();
 
 
    createPost = (e) => {
        e.preventDefault();
 
        const post = {
            author: this.authorRef.current.value,
            title: this.titleRef.current.value,
            body: this.contentRef.current.value,
            category: this.categoryRef.current.value
        }
 
        this.props.createPost(post);
 
    }
 
 
    render() { 
        return ( 
            <form onSubmit={this.createPost} className="col-md-10">
                <legend className="text-center">Create New Post</legend>
 
                <div className="form-group">
                    <label>Title for the Post:</label>
                    <input type="text" ref={this.titleRef} className="form-control" placeholder="Title.." />
                </div>
 
                <div className="form-group">
                    <label>Author:</label>
                    <input type="text" ref={this.authorRef} className="form-control" placeholder="Tag your name.." />
                </div>
 
                <div className="form-group">
                    <label>Content:</label>
                    <textarea className="form-control" rows="7"cols="25" ref={this.contentRef} placeholder="Here write your content.."></textarea>
                </div>
 
                <div className="form-group">
                    <label>Category</label>
                <select ref={this.categoryRef} className="form-control">
                    <option value="cars">Cars</option>
                    <option value="nature">Nature</option>
                    <option value="it">IT</option>
                    <option value="books">Books</option>
                    <option value="sport">Sport</option>
                </select>
                </div>
                <button type="submit" className="btn btn-primary">Create</button>
            </form>
         );
    }
}
 
export default Form;

Load single post page

src\components\SinglePost.js

Example

 
import React, { Component } from 'react';
import moment from 'moment';
 
import Divider from '@material-ui/core/Divider';
import Paper from '@material-ui/core/Paper';
import renderHTML from 'react-render-html';
 
class SinglePost extends Component {
 
    showPost = (props) => {
        if (!props.post) return null;
 
        const {title, author, body, category, datestamp} = this.props.post;
 
        return (
            <React.Fragment>
 
                <Paper className="single_post"> 
                    <h4>Title: {title}</h4>
                    <Divider light />
                    <p><b>Autor:</b> {author}</p>
                    <Divider light />
                    <p><b>Content:</b> {body}</p>
                    <Divider light />
                    <p><b>Category:</b> {category}</p>
                    <Divider light />
                    <h5>Create At: {moment(datestamp).format('DD MM YYYY')}</h5>
                    <div style={{ width: '60%' }}>{renderHTML(body)}</div>
                </Paper>
            </React.Fragment>
        )
 
    }
    render() {
        return (
            <div className=" col-md-10">
                {this.showPost(this.props)} 
            </div>
        );
    }
}
 
 
export default SinglePost;

Generate list of posts for listing page

src\components\Listing.js

Example

import React, { Component } from 'react';
import Post from './Post';
import './Post.css';
 
class Listing extends Component {
    showPosts = () => {
        const posts = this.props.posts;
        if (posts.length === 0) return null;        
        return (
            <div classname="post_list_item"><React.Fragment>
                {Object.keys(posts).map(post =>(
                    <Post
                        key={post}
                        info={this.props.posts[post]}
                        deletePost={this.props.deletePost}
                    />
 
                ) )}
            </React.Fragment></div>
        )
    }
 
    render() { 
        return ( 
                <div classname="post_list">
                    {this.showPosts() }
                </div>
 
 
         );
    }
}
 
export default Listing;

Post listing page

src\components\Posts.js

Example

import React, { Component } from 'react';
import Listing from './Listing';
import './Post.css';
 
class Posts extends Component {
    state = {  }
    render() { 
        return ( 
            <form className="col-md-10">
                <legend className="text-center">Post Listing Page</legend>
                <Listing 
                    posts={this.props.posts} 
                    deletePost={this.props.deletePost} 
                />
            </form>
         );
    }
}
 
export default Posts;

Post details in post listing page

src\components\Post.js

Example

import React, { Component } from 'react';
import {Link} from 'react-router-dom';
import Swal from 'sweetalert2';
import './Post.css';
import moment from 'moment';
import Paper from '@material-ui/core/Paper';
import Divider from '@material-ui/core/Divider';
 
class Post extends Component {
    confirmDeletion = () => {
        const {id} = this.props.info;
 
        Swal.fire({
                title: 'Delete this one?',
                text: "This action can not be canceled!",
                type: 'warning',
                showCancelButton: true,
                confirmButtonColor: '#3085d6',
                cancelButtonColor: '#d33',
                confirmButtonText: 'Yes, delete',
                cancelButtonText: 'No, Cancel'
          }).then((result) => {
            if (result.value) {
                this.props.deletePost(id)
                Swal.fire(
                    'Press OK to back',
                    'The post has been deleted',
                    'success'
                )
            }
          })
    }
 
 
    render() {
        const {id, title, body, category, datestamp} = this.props.info;
 
        return ( 
            <Paper className="post">
            <p className="post_title" cols="10">
                <b><span className='post-preview'>
                    {title.length > 25 ? `${title.substr(0, 25)}...` : title}
                </span></b>
            </p>
            <Divider light />
                <p className="post_body">
                    <span className='post-preview'>
                        {body.length > 300 ? `${body.substr(0, 300)}...` : body}
                    </span>
                </p>
                <Divider light />
                <p className="post_category"><b>{category}</b></p>
                <Divider light />
                <p className="post_datestamp"><b>{moment(datestamp).fromNow()}</b></p>                
                    <div className="post_button">
                        <ul className="buttons">
                            <li><Link to={`/post/${id}`} className="btn btn-primary"> Show </Link></li>
                            <li><Link to={`/edit/${id}`} className="btn btn-warning"> Edit </Link></li>
                            <li><Link onClick={this.confirmDeletion} className="btn btn-danger">Delete</Link></li>
                        </ul>
                    </div>                   
            </Paper>
         );
    }
}
export default Post;

Post Edit Page

src\components\EditPost.js

Example

import React, { Component } from 'react';
 
class EditPost extends Component {
    authorRef = React.createRef();
    titleRef = React.createRef();
    contentRef = React.createRef();
    categoryRef = React.createRef();
 
    editPost = (e) => {
        e.preventDefault();
        const post = {
            author: this.authorRef.current.value,
            title: this.titleRef.current.value,
            body: this.contentRef.current.value,
            category: this.categoryRef.current.value,
            id: this.props.post.id 
        }
        this.props.editPost(post);
    }
 
    loadForm = () => {
        if (!this.props.post) return null;
        const {title, author, body, category} = this.props.post;
 
        return (    
            <form onSubmit={this.editPost} className="col-md-10">
                <legend className="text-center">Edit Post</legend>
 
                <div className="form-group">
                    <label>Title for the Post:</label>
                    <input type="text" ref={this.titleRef} className="form-control" defaultValue={title} />
                </div>
 
                <div className="form-group">
                    <label>Author:</label>
                    <input type="text" ref={this.authorRef} className="form-control" defaultValue={author} />
                </div>
 
                <div className="form-group">
                    <label>Content:</label>
                    <textarea className="form-control" rows="7"cols="25" ref={this.contentRef} defaultValue={body}></textarea>
                </div>
 
                <div className="form-group">
                    <label>Category: </label>
                <select ref={this.categoryRef} className="form-control" defaultValue={category}>
                    <option value="cars">Cars</option>
                    <option value="nature">Nature</option>
                    <option value="it">IT</option>
                    <option value="books">Books</option>
                    <option value="sport">Sport</option>
                </select>
                </div>
 
                <button type="submit" className="btn btn-primary" >Save changes</button>
            </form>
        );
    }
 
 
    render() {
        return ( 
            <React.Fragment>
                {this.loadForm()}
            </React.Fragment>            
         );
    }
}
 
export default EditPost;

Style for Post page

src\components\Post.css

Example

.post{
	display: grid;
	grid-template-columns: repeat(1, 1fr);
 
	float: left;
	margin-right: 10px;
	width: 250px;
	height: 450px;
	margin-top: 50px; 
 
 
}
ul li{
  list-style-type: none;
  display: inline;
 
 
}
 
.post_title{	
	font-size: 1em;
	margin-top: 10px;
	text-align: center;
}
.post_button{
	position: relative;
	display: inline-block;
 
}
.buttons {
	position: absolute;
	bottom: 0;
 
}
.post_body{
	margin: 10px;
	white-space: pre-wrap;
	text-align: left;
}
.post_list 
{
  	display: grid;
	grid-template-columns: repeat(3, 1fr);
 
}
.post_category{
	text-align: center;
}
.post_datestamp{
	text-align: center;
}

You need to replace the "src/App.js" content using the below code to load blog content

src\App.js

Example

import React, { Component } from 'react';
import Router from './components/Router';
 
class App extends Component {
  render() {
    return (
        <Router />
    );
  }
}
 
export default App;

Final folder structure

Example


Now launch the application

npm start

This will developed a static website on web server on port 3000 on your machine. This also launch your browser to navigate http://localhost:3000.


Most Helpful This Week