Build a Blog Site with Next.js and Firebase Part 5 - Editing & Deleting Posts
Published Tuesday, February 2, 2021 — 10 minute read
Welcome to the fifth and final post in my new "Build a Blog Site with Next.js and Firebase" series! This series is pretty similar to a series I wrote in 2019: "Build a React & Firebase Blog Site". Because it's been well over a year since I published that series, I decided to create a new series and use the Next.js React framework this time. It's a fun framework to use, and I know so many people that are curious about it. I hope you have enjoyed the series! As always, you can contact me through Twitter or email if you run into any issues.
- Read Part 1 of the series
- Read Part 2 of the series
- Read Part 3 of the series
- Read Part 4 of the series
Add a Page for Editing Posts
- Add a new
updatePost
function tolib/firebase.js
:
1/*
2Updates the data for the given post in the database.
3*/
4export const updatePost = async (post) => {
5 initFirebase();
6
7 return firebase.database().ref(`/posts/${post.slug}`).set(post);
8};
9
- Add
edit.module.scss
to thestyles
directory:
1.EditPage {
2 form {
3 h1 {
4 margin-top: 0;
5 margin-bottom: 24px;
6 }
7
8 input {
9 margin-bottom: 16px;
10 }
11
12 textarea {
13 height: 300px;
14 }
15
16 button {
17 display: block;
18 margin-right: 0;
19 margin-left: auto;
20 margin-top: 24px;
21 }
22 }
23}
24
- Add an
edit
directory topages
. - Add
[slug].js
topages/edit
directory:
1import { useState } from 'react';
2import { useRouter } from 'next/router';
3import { getPostBySlug, updatePost } from '@lib/firebase';
4import { useAuth } from '@contexts/auth';
5import { Layout } from '@components';
6import styles from '@styles/edit.module.scss';
7
8const EditPage = ({ post }) => {
9 const router = useRouter();
10 const [user, userLoading] = useAuth();
11 const [values, setValues] = useState(post);
12 const [isLoading, setIsLoading] = useState(false);
13
14 if (userLoading) {
15 return null;
16 }
17
18 if (!user && typeof window !== 'undefined') {
19 router.push('/signin');
20 return null;
21 }
22
23 const handleChange = (e) => {
24 const id = e.target.id;
25 const newValue = e.target.value;
26
27 setValues({ ...values, [id]: newValue });
28 };
29
30 const handleSubmit = (e) => {
31 e.preventDefault();
32
33 let missingValues = [];
34 Object.entries(values).forEach(([key, value]) => {
35 if (!value) {
36 missingValues.push(key);
37 }
38 });
39
40 if (missingValues.length > 1) {
41 alert(`You're missing these fields: ${missingValues.join(', ')}`);
42 return;
43 }
44
45 setIsLoading(true);
46 updatePost(values)
47 .then(() => {
48 setIsLoading(false);
49 router.push(`/post/${post.slug}`);
50 })
51 .catch((err) => {
52 alert(err);
53 setIsLoading(false);
54 });
55 };
56
57 return (
58 <Layout>
59 <div className={styles.EditPage}>
60 <form onSubmit={handleSubmit}>
61 <h1>Edit Post: {post.slug}</h1>
62 <div>
63 <label htmlFor="title">Title</label>
64 <input
65 id="title"
66 type="text"
67 value={values.title}
68 onChange={handleChange}
69 />
70 </div>
71 <div>
72 <label htmlFor="coverImage">Cover Image URL</label>
73 <input
74 id="coverImage"
75 type="text"
76 value={values.coverImage}
77 onChange={handleChange}
78 />
79 </div>
80 <div>
81 <label htmlFor="coverImageAlt">Cover Image Alt</label>
82 <input
83 id="coverImageAlt"
84 type="text"
85 value={values.coverImageAlt}
86 onChange={handleChange}
87 />
88 </div>
89 <div>
90 <label htmlFor="content">Content</label>
91 <textarea
92 id="content"
93 value={values.content}
94 onChange={handleChange}
95 />
96 </div>
97 <button type="submit" disabled={isLoading}>
98 {isLoading ? 'Updating...' : 'Update'}
99 </button>
100 </form>
101 </div>
102 </Layout>
103 );
104};
105
106export async function getServerSideProps(context) {
107 const post = await getPostBySlug(context.query.slug);
108
109 return {
110 props: {
111 post,
112 },
113 };
114}
115
116export default EditPage;
117
- Go to
http://localhost:3000/edit/my-first-blog-post
in your browser. It should look a lot like the create page. - Change something about the post and click "Update".
- You should be taken to the page for the post and should see your changes.
- Commit and push your work to your repository:
1git add .
2git commit -m "Adding EditPage component"
3git push
4
Install FontAwesome
- Install the
@fortawesome/fontawesome-svg-core
,@fortawesome/free-solid-svg-icons
, and@fortawesome/react-fontawesome
packages:
1npm install @fortawesome/fontawesome-svg-core @fortawesome/free-solid-svg-icons @fortawesome/react-fontawesome
2
- Load the icons in
pages/_app.js
:
1import { library } from '@fortawesome/fontawesome-svg-core';
2import { fas } from '@fortawesome/free-solid-svg-icons';
3
4library.add(fas);
5
- Commit and push your work to your repository:
1git add .
2git commit -m "Installing FontAwesome"
3git push
4
Add an Icon component
We're going to make a wrapper component for the FontAwesomeIcon
to make it easy to render an icon at different sizes and so we don't have to remember the import path for the FontAwesomeIcon
component. The classnames
package is great conditionally applying classes based on boolean props passed to a component. You can read more about the package here.
- Install the
classnames
package:
1npm install classnames
2
- Add
Icon
directory to thecomponents
directory. - Add
Icon.module.scss
to thecomponents/Icon
directory:
1$icon-small: 1rem;
2$icon-medium: 1.5rem;
3$icon-large: 2rem;
4
5.Icon {
6 width: $icon-medium;
7 height: $icon-medium;
8 font-size: $icon-medium;
9
10 &--small {
11 width: $icon-small;
12 height: $icon-small;
13 font-size: $icon-small;
14 }
15
16 &--large {
17 width: $icon-large;
18 height: $icon-large;
19 font-size: $icon-large;
20 }
21}
22
- Add
Icon.jsx
to thecomponents/Icon
directory:
1import classNames from 'classnames/bind';
2import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3import styles from './Icon.module.scss';
4
5const Icon = ({ name, small, large }) => {
6 let cx = classNames.bind(styles);
7 let className = cx('Icon', {
8 'Icon--small': small,
9 'Icon--large': large,
10 });
11
12 return <FontAwesomeIcon className={className} icon={name} />;
13};
14
15export default Icon;
16
- Update
components/index.js
to exportIcon
:
1export { default as Icon } from './Icon/Icon';
2
- Commit and push your work to your repository:
1git add .
2git commit -m "Adding Icon component"
3git push
4
Add a Link for EditPage
- Import the new
Icon
component inPostPage
(that'spages/post/[slug.js]
):
1import { Icon, Layout } from '@components';
2
- Also import
useAuth
inPostPage
:
1import { useAuth } from '@contexts/auth';
2
- Invoke
useAuth
afteruseRouter
inPostPage
:
1const [user] = useAuth();
2
- Wrap the
<h1>
with the post's title in a<div>
and also render an edit link in the<div>
if there's an authenticated user:
1<div>
2 <h1>{post.title}</h1>
3 {user && (
4 <a href={`/edit/${post.slug}`}>
5 <Icon name="pencil-alt" />
6 </a>
7 )}
8</div>
9
- After the
<h1>
styles instyles/post.module.scss
, add styles for the<a>
element:
1a {
2 padding: 8px;
3 color: black;
4}
5
- Go to
http://localhost:3000/post/my-first-blog-post
in your browser. - Click the new pencil icon.
- You should be taken to the edit page for
my-first-blog-post
. - Commit and push your work to your repository:
1git add .
2git commit -m "Adding edit link"
3git push
4
Add a Delete Button
- Add a new
deletePost
function inlib/firebase.js
:
1/*
2Deletes a post from the database.
3*/
4export const deletePost = async (slug) => {
5 initFirebase();
6
7 return firebase.database().ref(`/posts/${slug}`).set(null);
8};
9
- Import the
deletePost
function inPostPage
:
1import { deletePost, getPostBySlug } from '@lib/firebase';
2
- At the same level as the edit link we just added in the previous section, add a
<button>
and wrap both elements in adiv
:
1<span>
2 <a href={`/edit/${post.slug}`}>
3 <Icon name="pencil-alt" />
4 </a>
5 <button
6 onClick={() => {
7 const shouldDeletePost = confirm(
8 'Are you sure you want to delete this post?',
9 );
10 if (shouldDeletePost) {
11 deletePost(post.slug).then(() => {
12 router.push('/');
13 });
14 }
15 }}
16 >
17 <Icon name="trash-alt" />
18 </button>
19</span>
20
- Replace the
a
styles you added tostyles/post.module.scss
with these:
1& > div {
2 display: flex;
3 margin: 0;
4
5 a {
6 padding: 8px;
7 color: black;
8 }
9
10 button {
11 background: transparent;
12 box-shadow: none;
13 border: none;
14 color: black;
15 }
16}
17
- Create a new post for testing the delete functionality.
- Go to the post's page and you should see a trashcan icon to the right of the pencil icon like this:
- Click the trashcan icon and you should see an alert that says "Are you sure you want to delete this post?"
- Click OK and you should be taken back to the home page. That post will no longer be listed and it should also be gone from the database.
- Commit and push your work to your repository:
1git add .
2git commit -m "Adding delete button"
3git push
4
- Celebrate!!! You did it!!! 🎉