Skip Navigation
Go to home page

Build a Blog Site with Next.js and Firebase Part 5 - Editing & Deleting Posts

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.


Add a Page for Editing Posts

  1. Add a new updatePost function to lib/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
  1. Add edit.module.scss to the styles 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
  1. Add an edit directory to pages.
  2. Add [slug].js to pages/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
  1. Go to http://localhost:3000/edit/my-first-blog-post in your browser. It should look a lot like the create page.
  2. Change something about the post and click "Update".
  3. You should be taken to the page for the post and should see your changes.
  4. Commit and push your work to your repository:
1git add .
2git commit -m "Adding EditPage component"
3git push
4

Install FontAwesome

  1. 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
  1. 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
  1. 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.

  1. Install the classnames package:
1npm install classnames
2
  1. Add Icon directory to the components directory.
  2. Add Icon.module.scss to the components/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
  1. Add Icon.jsx to the components/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
  1. Update components/index.js to export Icon:
1export { default as Icon } from './Icon/Icon';
2
  1. Commit and push your work to your repository:
1git add .
2git commit -m "Adding Icon component"
3git push
4
  1. Import the new Icon component in PostPage (that's pages/post/[slug.js]):
1import { Icon, Layout } from '@components';
2
  1. Also import useAuth in PostPage:
1import { useAuth } from '@contexts/auth';
2
  1. Invoke useAuth after useRouter in PostPage:
1const [user] = useAuth();
2
  1. 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
  1. After the <h1> styles in styles/post.module.scss, add styles for the <a> element:
1a {
2  padding: 8px;
3  color: black;
4}
5
  1. Go to http://localhost:3000/post/my-first-blog-post in your browser.
  2. Click the new pencil icon.
  3. You should be taken to the edit page for my-first-blog-post.
  4. Commit and push your work to your repository:
1git add .
2git commit -m "Adding edit link"
3git push
4

Add a Delete Button

  1. Add a new deletePost function in lib/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
  1. Import the deletePost function in PostPage:
1import { deletePost, getPostBySlug } from '@lib/firebase';
2
  1. At the same level as the edit link we just added in the previous section, add a <button> and wrap both elements in a div:
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
  1. Replace the a styles you added to styles/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
  1. Create a new post for testing the delete functionality.
  2. Go to the post's page and you should see a trashcan icon to the right of the pencil icon like this:

View of the trashcan and other icon.

  1. Click the trashcan icon and you should see an alert that says "Are you sure you want to delete this post?"
  2. 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.
  3. Commit and push your work to your repository:
1git add .
2git commit -m "Adding delete button"
3git push
4
  1. Celebrate!!! You did it!!! ğŸŽ‰
Back to Top