Creating a Blogging App Using React, Part 3: Add & Display Posts
In the previous parts of this tutorial series, you saw how to implement the sign-up and sign-in functionality. In this part of the tutorial, you'll implement the user home page and allow the adding and displaying of blog posts.
I've created a simple UX layout to help you visualise the landing page of the blog. This will help you follow the post better. The functionalities to be covered are:
- how to pass data between two pages via the route
- how to save a post
- how to open the chosen post



Getting Started
Let's get started by cloning the source code from this repository. Once the repository is cloned, you will be able to see the client-side code. Navigate to the project directory, and run the following command to install all the essential dependencies.
1 |
npm install |
2 |
npm start |
The Server
So far in the tutorial, we have been able to register and check if a user is present with the system. Now, we will see how to add and retrieve posts. To do this, we need to add a few more endpoints in the express server.
To get all posts from the database, we'll use the following get
call.
1 |
app.get('/api/get/allposts', (req, res, next ) => { |
2 |
pool.query(`SELECT * FROM posts`, |
3 |
(q_err, q_res) => { |
4 |
res.json(q_res.rows) |
5 |
})
|
6 |
})
|
To get a specific post from the database, the query should contain the required post id. With this information, the following get
endpoint can be used to retrieve a specific post.
1 |
app.get('/api/get/post', (req, res, next) => { |
2 |
const post_id = req.query.post_id |
3 |
|
4 |
pool.query(`SELECT * FROM posts |
5 |
WHERE pid=$1`, |
6 |
[ post_id ], (q_err, q_res) => { |
7 |
res.json(q_res.rows) |
8 |
})
|
9 |
} ) |
When you want to insert
a new post into the database, you can use the following endpoint with a post
call.
1 |
app.post('/api/post/posttodb', (req, res, next) => { |
2 |
const values = [ req.body.title, |
3 |
req.body.body, |
4 |
req.body.uid, |
5 |
req.body.username] |
6 |
pool.query(`INSERT INTO posts(title, body, user_id, author, date_created) |
7 |
VALUES($1, $2, $3, $4, NOW() )`, |
8 |
values, (q_err, q_res) => { |
9 |
if(q_err) return next(q_err); |
10 |
res.json(q_res.rows) |
11 |
})
|
12 |
})
|
The above changes are adequate to prepare the client for displaying and adding new posts to the database. The previous posts talk about how to set up the server and get started with the project.
The Client
A little recap: so far in the series, the client application has a sign-in and sign-up page. In this tutorial, we are going to build the landing page and a page for displaying the post.
We will modify our initial routes array to include a new route for /post
and another for /landing
.
1 |
const router = createBrowserRouter([ |
2 |
{
|
3 |
path: "/", |
4 |
element: <App/>, |
5 |
},
|
6 |
.
|
7 |
.
|
8 |
{
|
9 |
path: "/landing", |
10 |
element: <Landing />, |
11 |
},
|
12 |
{
|
13 |
path: "/post", |
14 |
element: <Post />, |
15 |
}
|
16 |
]);
|
The Landing Page
Once the user signs in or signs up, they will be redirected to the landing page. /landing/index.js will have the code discussed below.
During redirection, the email, UID, and user name will be sent to the landing page. On the landing page, we will extract this information from the location.
1 |
import { useLocation } from 'react-router-dom'; |
2 |
.
|
3 |
.
|
4 |
const { state } = useLocation(); |
5 |
const { email, username, uid } = state; |
We are going to define the following state variables:
-
showModal
: decide if the modal has to be shown or not. -
posts
: array of posts to be shown to the user on the landing page. By default, it will be an empty array. -
refresh
: reload the page, if true.
1 |
const [showModal, setShowModal] = useState(false) |
2 |
const [refresh, setRefresh] = useState(false) |
3 |
const [posts, setPosts] = useState([]) |
4 |
When the landing page is loaded, the useEffect
hook will be called. The []
in the definition of useEffect
specifies that useEffect
will be called only once. Inside this useEffect
, we will load all the user's posts.
1 |
useEffect(()=>{ |
2 |
loadAllPostsOfUser() |
3 |
},[])
|
The loadAllPostsOfUser
will use the /api/get/allposts
endpoint to fetch all the user's posts. Once the response arrives, we will populate the state posts
. An interesting thing about React functional hooks is that changes will reflect automatically. When the state of the post
changes, the UI will render again.
1 |
const loadAllPostsOfUser = () => { |
2 |
axios.get('/api/get/allposts') |
3 |
.then(res => setPosts(res.data) ) |
4 |
.catch((err) => console.log(err)) |
5 |
}
|
Now that we have the posts, let's build the UI as seen in the mock UX. For this, we will iterate through the posts and display the post title. On clicking the post title, we will navigate to a new route called /post
. During navigation, we'll send details of the post_id
to /post
to load the right post.
1 |
const openPost = (post_id) => { |
2 |
navigate(`/post`, { |
3 |
email: email, |
4 |
uid: uid, |
5 |
username: username, |
6 |
post_id: post_id |
7 |
})
|
8 |
}
|
9 |
.
|
10 |
.
|
11 |
return ( |
12 |
<div className="post_container"> |
13 |
{posts.map((post, index) => |
14 |
<React.Fragment> |
15 |
<span>{index + 1}</span> |
16 |
<span className="post_title" onClick={()=>openPost(post.id)}>{post.name}</span> |
17 |
</React.Fragment> |
18 |
)}
|
19 |
</div> |
20 |
)
|
Now, let's work on the header of the landing page. The header has the following items:
- a button to open a modal for creating a new post
- a button to sign out of the application
- a title that welcomes the user, along with their email ID. The username and email are fetched from the location object.
1 |
const openAddNewPostModal = () => { |
2 |
setShowModal(true) |
3 |
}
|
4 |
.
|
5 |
.
|
6 |
.
|
7 |
<div className='title'> |
8 |
Hello {username} [{email}]! |
9 |
<button onClick={()=>openAddNewPostModal()}>Add New Post</button> |
10 |
<button onClick={()=>signOutOfSystem()}>Sign out</button> |
11 |
</div> |
Next, we are going to create modal.js and modal.css. The modal will be seen when the user clicks on the Add New Post button. The modal appears below.



The modal takes the following properties:
-
handleClose
: a function that will be called when the user closes the modal. -
show
: a boolean parameter that decides if the modal should be shown or hidden -
children
: a Node that has the elements to be shown inside the modal
1 |
const Modal = ({ handleClose, show, children }) => { |
2 |
...
|
3 |
};
|
The modal strongly depends on its CSS. We will import the modal's CSS from modal.css. If the modal is shown, the parameter show
will be true
. With show
equal to true
, the classname display-block
with the property display: block
will be applied to the body of the modal. This will make the children visible in the modal. And when show
is false
, display-none
with the property display:none
will be applied. This will hide the children inside the modal.
1 |
import './modal.css'; |
2 |
|
3 |
const Modal = ({ handleClose, show, children }) => { |
4 |
const showHideClassName = show ? "modal display-block" : "modal display-none"; |
5 |
.... |
6 |
}; |
Now, showing children
elements inside the modal is simple. The modal has a button
that can be used to close and hide it. Modal.css has the styling required to change the look and feel of the modal. And {children}
will be passed from landing.js.
1 |
import './modal.css'; |
2 |
|
3 |
const Modal = ({ handleClose, show, children }) => { |
4 |
const showHideClassName = show ? "modal display-block" : "modal display-none"; |
5 |
|
6 |
return ( |
7 |
<div className={showHideClassName}> |
8 |
<section className="modal-main"> |
9 |
{children} |
10 |
<button className="buttonPosition" type="button" onClick={handleClose}> |
11 |
Close
|
12 |
</button> |
13 |
</section> |
14 |
</div> |
15 |
);
|
16 |
};
|
Modal.css will have the following code.
1 |
.modal { |
2 |
position: fixed; |
3 |
top: 0; |
4 |
left: 0; |
5 |
width:100%; |
6 |
height: 100%; |
7 |
background: rgba(0, 0, 0, 0.6); |
8 |
}
|
9 |
|
10 |
.modal-main { |
11 |
position:fixed; |
12 |
background: white; |
13 |
width: 80%; |
14 |
height: 500px; |
15 |
border-radius: 20px; |
16 |
top:50%; |
17 |
left:50%; |
18 |
transform: translate(-50%,-50%); |
19 |
display: flex; |
20 |
flex-direction: column; |
21 |
padding: 50px; |
22 |
}
|
23 |
|
24 |
.display-block { |
25 |
display: block; |
26 |
}
|
27 |
|
28 |
.display-none { |
29 |
display: none; |
30 |
}
|
Our next goal is to call modal.js from landing.js. For this, we will use the showModal
state variable to decide if the modal needs to be visible or not. handleClose
is a function passed from landing.js to modal.js. handleClose
will set the value of showModal
. The handleSubmit
function will call the endpoint /api/post/posttodb with details of the post and the user. This endpoint will add a new post in the database.
1 |
... |
2 |
import './index.css'; |
3 |
import Modal from './modal' |
4 |
... |
5 |
|
6 |
function Landing() { |
7 |
... |
8 |
const { email, username, uid } = state; |
9 |
const [showModal, setShowModal] = useState(false) |
10 |
... |
11 |
|
12 |
useEffect(()=>{ |
13 |
setShowModal(false) |
14 |
}, [refresh]) |
15 |
|
16 |
... |
17 |
|
18 |
const openAddNewPostModal = () => { |
19 |
setShowModal(true) |
20 |
} |
21 |
|
22 |
const handleSubmit = (event) => { |
23 |
event.preventDefault() |
24 |
const data = {title: event.target.title.value, |
25 |
body: event.target.body.value, |
26 |
username: username, |
27 |
uid: uid} |
28 |
|
29 |
axios.post('/api/post/posttodb', data) |
30 |
.then(response => console.log(response)) |
31 |
.catch((err) => console.log(err)) |
32 |
.then(setTimeout(() => setRefresh(!refresh), 700) ) |
33 |
} |
34 |
|
35 |
return ( |
36 |
<div className="container"> |
37 |
... |
38 |
<Modal show={showModal} handleClose={()=>setShowModal(!showModal)}> |
39 |
<form onSubmit={handleSubmit} className='modalContainer'> |
40 |
<span>Title of the Blog</span><input type='text'/> |
41 |
<span>Subject</span><textarea/> |
42 |
<button type='submit'>Submit</button> |
43 |
</form> |
44 |
</Modal> |
45 |
... |
46 |
</div> |
47 |
); |
48 |
} |
49 |
|
50 |
export default Landing; |
Once the post is successfully added to the database, the value of refresh
will be toggled. Any change in the refresh
value will call the useEffect
shown below. This will set false
to showModal
and hide the modal. Also, the whole landing.js page will be reloaded. Consequently, the newly added post will be seen in the list of posts.
1 |
useEffect(()=>{ |
2 |
setShowModal(false) |
3 |
}, [refresh]) |
The last step in adding a post is to show it in the UI. For this, we will create two files: post.js and post.css. The post.js page will be loaded when the user clicks on the image name in landing.js. Our post.js page appears as below.



Upon loading post.js, the post_id
, usename
, email
, and uid
will be available through the useLocation
hook. With the post_id
, we will call the /api/get/post endpoint to fetch the post.
1 |
useEffect(()=>{ |
2 |
if(post_id && uid) { |
3 |
axios.get('/api/get/post', |
4 |
{params: {post_id: post_id}} ) |
5 |
.then(res => res.data.length !== 0 |
6 |
? setPost({likes: res.data[0].likes, |
7 |
like_user_ids: res.data[0].like_user_id, |
8 |
post_title: res.data[0].title, |
9 |
post_body: res.data[0].body, |
10 |
post_author: res.data[0].author |
11 |
})
|
12 |
: null |
13 |
)
|
14 |
.catch((err) => console.log(err) ) |
15 |
}
|
16 |
}, [post_id]) |
Once the post is retrieved from the database, the state variable post
will be populated with its details. As soon as the state variable is modified, the UI will render again with the post. We will use the optional chaining operator (?
) to make sure the UI does not break when the state variable post
is empty.
1 |
import React, { useEffect, useState } from 'react'; |
2 |
import { useLocation } from 'react-router-dom'; |
3 |
import './post.css'; |
4 |
import './index.css'; |
5 |
import axios from 'axios'; |
6 |
|
7 |
function Post() { |
8 |
const { state } = useLocation(); |
9 |
const { email, username, uid, post_id } = state || {username: 'Tuts+ Envato', email: 'tuts@envato.com', uid: '123', post_id: 1} |
10 |
const [post, setPost] = useState() |
11 |
|
12 |
useEffect(()=>{ |
13 |
if(post_id && uid) { |
14 |
axios.get('/api/get/post', |
15 |
{params: {post_id: post_id}} ) |
16 |
.then(res => res.data.length !== 0 |
17 |
? setPost({likes: res.data[0].likes, |
18 |
like_user_ids: res.data[0].like_user_id, |
19 |
post_title: res.data[0].title, |
20 |
post_body: res.data[0].body, |
21 |
post_author: res.data[0].author |
22 |
}) |
23 |
: null |
24 |
) |
25 |
.catch((err) => console.log(err) ) |
26 |
} |
27 |
}, [post_id]) |
28 |
|
29 |
const signOutOfSystem = () => { |
30 |
... |
31 |
} |
32 |
|
33 |
return ( |
34 |
<div className="container"> |
35 |
<div className='title'> |
36 |
Hello {username} [{email}]! |
37 |
|
38 |
{!uid && <div>Seems like you are not logged in!</div>} |
39 |
<button onClick={()=>signOutOfSystem()}>Sign out</button> |
40 |
</div> |
41 |
<br/> |
42 |
<div> |
43 |
{post?.post_title} by {post.post_author} |
44 |
</div> |
45 |
<br/> |
46 |
<br/> |
47 |
<section> |
48 |
{post?.post_body} |
49 |
</section> |
50 |
<br/> |
51 |
<br/> |
52 |
<aside> |
53 |
{post.likes > 0 ? <div>Liked by {post?.likes} readers</div>: null} |
54 |
</aside> |
55 |
</div> |
56 |
); |
57 |
} |
58 |
|
59 |
export default Post; |
Conclusion
With this, we have built landing.js, modal.js, and post.js to add and show posts in our blog. Also, we have designed and developed the get post, add post, and show all posts endpoints in our Express server.
The entire workflow is extremely interesting. It has many components that come together quite easily with the help of functional React hooks. We have taken a closer look at a few commonly used hooks like useLocation
, useNavigation
, useState
, and useEffect
. As you start building this blog by yourself, you will see how often these hooks are used in React.
The next part of this tutorial focuses on how to edit and delete a post. Try to implement this functionality yourself—before reading the next post!