Ashlee standing in front of a brick wall looking up to her left and smiling. Her right hand is held up near her right shoulder with the palm facing upwards.

Ashlee M Boyer

You can find me talking about issues surrounding Disability, Accessibility, & Mental Health on Twitter, or you can find me regularly live-knitting or live-coding on Twitch.

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

Part of the Build a Blog Site with Next.js and Firebase Series

Tuesday, February 2, 2021 — 10 minute read

#react#nextjs#firebase#web-development

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.

Table of Contents


Add a Page for Editing Posts

  1. Add a new updatePost function to lib/firebase.js:
js
12345678
/*Updates the data for the given post in the database.*/export const updatePost = async (post) => {  initFirebase();   return firebase.database().ref(`/posts/${post.slug}`).set(post);};
  1. Add edit.module.scss to the styles directory:
scss
1234567891011121314151617181920212223
.EditPage {  form {    h1 {      margin-top: 0;      margin-bottom: 24px;    }     input {      margin-bottom: 16px;    }     textarea {      height: 300px;    }     button {      display: block;      margin-right: 0;      margin-left: auto;      margin-top: 24px;    }  }}
  1. Add an edit directory to pages.
  2. Add [slug].js to pages/edit directory:
jsx
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
import { useState } from 'react';import { useRouter } from 'next/router';import { getPostBySlug, updatePost } from '@lib/firebase';import { useAuth } from '@contexts/auth';import { Layout } from '@components';import styles from '@styles/edit.module.scss'; const EditPage = ({ post }) => {  const router = useRouter();  const [user, userLoading] = useAuth();  const [values, setValues] = useState(post);  const [isLoading, setIsLoading] = useState(false);   if (userLoading) {    return null;  }   if (!user && typeof window !== 'undefined') {    router.push('/signin');    return null;  }   const handleChange = (e) => {    const id = e.target.id;    const newValue = e.target.value;     setValues({ ...values, [id]: newValue });  };   const handleSubmit = (e) => {    e.preventDefault();     let missingValues = [];    Object.entries(values).forEach(([key, value]) => {      if (!value) {        missingValues.push(key);      }    });     if (missingValues.length > 1) {      alert(`You're missing these fields: ${missingValues.join(', ')}`);      return;    }     setIsLoading(true);    updatePost(values)      .then(() => {        setIsLoading(false);        router.push(`/post/${post.slug}`);      })      .catch((err) => {        alert(err);        setIsLoading(false);      });  };   return (    <Layout>      <div className={styles.EditPage}>        <form onSubmit={handleSubmit}>          <h1>Edit Post: {post.slug}</h1>          <div>            <label htmlFor="title">Title</label>            <input              id="title"              type="text"              value={values.title}              onChange={handleChange}            />          </div>          <div>            <label htmlFor="coverImage">Cover Image URL</label>            <input              id="coverImage"              type="text"              value={values.coverImage}              onChange={handleChange}            />          </div>          <div>            <label htmlFor="coverImageAlt">Cover Image Alt</label>            <input              id="coverImageAlt"              type="text"              value={values.coverImageAlt}              onChange={handleChange}            />          </div>          <div>            <label htmlFor="content">Content</label>            <textarea              id="content"              value={values.content}              onChange={handleChange}            />          </div>          <button type="submit" disabled={isLoading}>            {isLoading ? 'Updating...' : 'Update'}          </button>        </form>      </div>    </Layout>  );}; export async function getServerSideProps(context) {  const post = await getPostBySlug(context.query.slug);   return {    props: {      post,    },  };} export default EditPage;
  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:
123
git add .git commit -m "Adding EditPage component"git push

Install FontAwesome

  1. Install the @fortawesome/fontawesome-svg-core, @fortawesome/free-solid-svg-icons, and @fortawesome/react-fontawesome packages:
bash
1
npm install @fortawesome/fontawesome-svg-core @fortawesome/free-solid-svg-icons @fortawesome/react-fontawesome
  1. Load the icons in pages/_app.js:
jsx
1234
import { library } from '@fortawesome/fontawesome-svg-core';import { fas } from '@fortawesome/free-solid-svg-icons'; library.add(fas);
  1. Commit and push your work to your repository:
123
git add .git commit -m "Installing FontAwesome"git push

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:
1
npm install classnames
  1. Add Icon directory to the components directory.
  2. Add Icon.module.scss to the components/Icon directory:
scss
123456789101112131415161718192021
$icon-small: 1rem;$icon-medium: 1.5rem;$icon-large: 2rem; .Icon {  width: $icon-medium;  height: $icon-medium;  font-size: $icon-medium;   &--small {    width: $icon-small;    height: $icon-small;    font-size: $icon-small;  }   &--large {    width: $icon-large;    height: $icon-large;    font-size: $icon-large;  }}
  1. Add Icon.jsx to the components/Icon directory:
jsx
123456789101112131415
import classNames from 'classnames/bind';import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';import styles from './Icon.module.scss'; const Icon = ({ name, small, large }) => {  let cx = classNames.bind(styles);  let className = cx('Icon', {    'Icon--small': small,    'Icon--large': large,  });   return <FontAwesomeIcon className={className} icon={name} />;}; export default Icon;
  1. Update components/index.js to export Icon:
js
1
export { default as Icon } from './Icon/Icon';
  1. Commit and push your work to your repository:
123
git add .git commit -m "Adding Icon component"git push
  1. Import the new Icon component in PostPage (that's pages/post/[slug.js]):
jsx
1
import { Icon, Layout } from '@components';
  1. Also import useAuth in PostPage:
jsx
1
import { useAuth } from '@contexts/auth';
  1. Invoke useAuth after useRouter in PostPage:
jsx
1
const [user] = useAuth();
  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:
jsx
12345678
<div>  <h1>{post.title}</h1>  {user && (    <a href={`/edit/${post.slug}`}>      <Icon name="pencil-alt" />    </a>  )}</div>
  1. After the <h1> styles in styles/post.module.scss, add styles for the <a> element:
scss
1234
a {  padding: 8px;  color: black;}
  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:
123
git add .git commit -m "Adding edit link"git push

Add a Delete Button

  1. Add a new deletePost function in lib/firebase.js:
js
12345678
/*Deletes a post from the database.*/export const deletePost = async (slug) => {  initFirebase();   return firebase.database().ref(`/posts/${slug}`).set(null);};
  1. Import the deletePost function in PostPage:
jsx
1
import { deletePost, getPostBySlug } from '@lib/firebase';
  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:
jsx
12345678910111213141516171819
<span>  <a href={`/edit/${post.slug}`}>    <Icon name="pencil-alt" />  </a>  <button    onClick={() => {      const shouldDeletePost = confirm(        'Are you sure you want to delete this post?',      );      if (shouldDeletePost) {        deletePost(post.slug).then(() => {          router.push('/');        });      }    }}  >    <Icon name="trash-alt" />  </button></span>
  1. Replace the a styles you added to styles/post.module.scss with these:
scss
12345678910111213141516
& > div {  display: flex;  margin: 0;   a {    padding: 8px;    color: black;  }   button {    background: transparent;    box-shadow: none;    border: none;    color: black;  }}
  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:

  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:
123
git add .git commit -m "Adding delete button"git push
  1. Celebrate!!! You did it!!! ğŸŽ‰