Build a Blog Site with Next.js and Firebase Part 2 - Creating New Posts
Published Monday, January 11, 2021 — 13 minute read
Welcome to the second 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 enjoy the series!
- Read Part 1 of the series
- Read Part 3 of the series
- Read Part 4 of the series
- Read Part 5 of the series
Add a Page for Creating Posts
First, let's add styles and a minimal component for our new CreatePage
component.
- Add
create.module.scss
in thestyles
directory:
1.CreatePage {
2 max-width: 700px;
3 margin: 0 auto;
4 padding: 24px;
5
6 form {
7 padding: 16px;
8
9 h1 {
10 margin-top: 0;
11 margin-bottom: 24px;
12 }
13
14 input {
15 margin-bottom: 16px;
16 }
17
18 textarea {
19 height: 300px;
20 }
21
22 button {
23 display: block;
24 margin-right: 0;
25 margin-left: auto;
26 margin-top: 24px;
27 }
28 }
29}
30
- Add
create.js
in thepages
directory:
1import styles from './create.module.scss';
2
3const CreatePage = () => (
4 <div styles={className.CreatePage}>
5 <h1>Hello, from CreatePage!</h1>
6 </div>
7);
8
9export default CreatePage;
10
- Go to http://localhost:3000/create in your browser. There's not much here because we first want to check that the files we've added work correctly before we add too much functionality. It should look like this:
- Commit and push your work to your repository:
1git add .
2git commit -m "Adding basic CreatePage component"
3git push
4
Make a Form for Describing Posts
Now that we've successfully added a page for creating posts, we can add a handful of inputs we'll use to describe posts. Let's review what attributes our posts have so we can decide what inputs we need. You can double-check the "shape" of posts by opening up your Firebase Realtime Database and expanding the objects in it. You should see these attributes (they're listed in alphabetical order in Firebase):
title
slug
dateCreated
coverImage
coverImageAlt
content
Of all of these attributes, there's one we can auto-generate when that "Create" button is clicked and don't need an input for: dateCreated
. The rest need to be text <input>
s and the content
attribute will need a <textarea>
so it's easy to write a lot of text. Let's add some styles for these new controls we're adding and then add them to the CreatePage
.
- Add styles for the new elements we're adding to the end of
styles/global.scss
:
1label,
2input,
3textarea {
4 display: block;
5 width: 100%;
6}
7
8label {
9 margin-bottom: 4px;
10}
11
12input,
13textarea {
14 padding: 4px;
15 font-size: 1rem;
16 font-family: Arial, Helvetica, sans-serif;
17 border: 1px solid black;
18 border-radius: 4px;
19}
20
21button {
22 background-color: #1a73e8;
23 border: 1px solid #1a73e8;
24 border-radius: 4px;
25 padding: 8px;
26 color: white;
27 font-size: 1rem;
28 cursor: pointer;
29}
30
- Add some event handler functions and a
<form>
with the<input>
,<textarea>
, and<button>
elements we need toCreatePage
:
1import { useState } from 'react';
2import styles from '@styles/create.module.scss';
3
4const CreatePage = () => {
5 const [formValues, setFormValues] = useState({
6 title: '',
7 slug: '',
8 coverImage: '',
9 coverImageAlt: '',
10 content: '',
11 });
12
13 /*
14 This is the function we're passing to each control so we can capture
15 the value in it and store it in our `formValues` variable.
16 */
17 const handleChange = (e) => {
18 const id = e.target.id;
19 const newValue = e.target.value;
20
21 setFormValues({ ...formValues, [id]: newValue });
22 };
23
24 /*
25 This function is passed to the <form> and specifies what happens when
26 the form is submitted. For now, we're going to log our `formValues`
27 to verify that they are being managed correctly.
28
29 Side note: we do not need to set an `onClick` for the <button> at the
30 end of the form because it has type="submit". This allows us to click
31 to submit the form or press the Enter key to submit it.
32 */
33 const handleSubmit = (e) => {
34 // This prevents the default functionality of submitting a form
35 e.preventDefault();
36
37 console.log(formValues);
38 };
39
40 return (
41 <div className={styles.CreatePage}>
42 <form onSubmit={handleSubmit}>
43 <h1>Create a new post</h1>
44 <div>
45 <label htmlFor="title">Title</label>
46 <input
47 id="title"
48 type="text"
49 value={formValues.title}
50 onChange={handleChange}
51 />
52 </div>
53 <div>
54 <label htmlFor="slug">Slug</label>
55 <input
56 id="slug"
57 type="text"
58 value={formValues.slug}
59 onChange={handleChange}
60 />
61 </div>
62 <div>
63 <label htmlFor="coverImage">Cover Image URL</label>
64 <input
65 id="coverImage"
66 type="text"
67 value={formValues.coverImage}
68 onChange={handleChange}
69 />
70 </div>
71 <div>
72 <label htmlFor="coverImageAlt">Cover Image Alt</label>
73 <input
74 id="coverImageAlt"
75 type="text"
76 value={formValues.coverImageAlt}
77 onChange={handleChange}
78 />
79 </div>
80 <div>
81 <label htmlFor="content">Content</label>
82 <textarea
83 id="content"
84 value={formValues.content}
85 onChange={handleChange}
86 />
87 </div>
88 <button type="submit">Create</button>
89 </form>
90 </div>
91 );
92};
93
94export default CreatePage;
95
- Enter some data into each of the controls. Here's a list of what I'm entering as an example:
title
: My Third Blog Postslug
: my-third-blog-postcoverImage
: https://placekitten.com/g/700/300coverImageAlt
: A random kitten from PlaceKitten.com.content
: Check me out! I'm writing my third blog post on a site I created. I'm so proud of myself!
- Click the "Create" button.
- Check the dev tools console.
- Your page and console should look like this:
- Commit and push your work to your repository:
1git add .
2git commit -m "Adding form and controls to CreatePage"
3git push
4
Write a Firebase Create Function
Our CreatePage
component captures all the data we need to create a post. Now, we need to do something with that data.
- Add a
createPost
function tolib/firebase.js
:
1/*
2Creates a new post under /posts in the Realtime Database. Automatically
3generates the `dateCreated` property from the current UTC time in milliseconds.
4*/
5export const createPost = async (post) => {
6 initFirebase();
7
8 const dateCreated = new Date().getTime();
9 post.dateCreated = dateCreated;
10
11 return firebase.database().ref(`/posts/${post.slug}`).set(post);
12};
13
- Update the
CreatePage
component for fully creating posts. The code after this list of changes shows what your component should look like after making the updates.
- Import
useRouter
fromnext/router
after theuseState
import. - Import
createPost
from@lib/firebase
after theuseRouter
import. - Instantiate the
router
at the top of theCreatePage
function. - Add an
isLoading
state after theformValues
instantiation. - Update
handleSubmit
to check for missing values, set theisLoading
state, and callcreatePost
. - Update the submit
<button>
to useisLoading
.
1import { useState } from 'react';
2import { useRouter } from 'next/router'; // this is new
3import { createPost } from '@lib/firebase'; // this is new
4import styles from '@styles/create.module.scss';
5
6const CreatePage = () => {
7 const router = useRouter(); // this is new
8 const [formValues, setFormValues] = useState({
9 title: '',
10 slug: '',
11 coverImage: '',
12 coverImageAlt: '',
13 content: '',
14 });
15 const [isLoading, setIsLoading] = useState(false); // this is new
16
17 /*
18 This is the function we're passing to each control so we can capture
19 the value in it and store it in our `formValues` variable.
20 */
21 const handleChange = (e) => {
22 const id = e.target.id;
23 const newValue = e.target.value;
24
25 setFormValues({ ...formValues, [id]: newValue });
26 };
27
28 /*
29 This function is passed to the <form> and specifies what happens when
30 the form is submitted. For now, we're going to log our `formValues`
31 to verify that they are being managed correctly.
32
33 Side note: we do not need to set an `onClick` for the <button> at the
34 end of the form because it has type="submit". This allows us to click
35 to submit the form or press the Enter key to submit it.
36 */
37 const handleSubmit = (e) => {
38 // This prevents the default functionality of submitting a form
39 e.preventDefault();
40
41 // Check if there are any missing values.
42 let missingValues = [];
43 Object.entries(formValues).forEach(([key, value]) => {
44 if (!value) {
45 missingValues.push(key);
46 }
47 });
48
49 // Alert and prevent the post from being created if there are missing values.
50 if (missingValues.length > 1) {
51 alert(`You're missing these fields: ${missingValues.join(', ')}`);
52 return;
53 }
54
55 // Update the isLoading state.
56 setIsLoading(true);
57
58 // Start the attempt to create a new post.
59 createPost(formValues)
60 .then(() => {
61 // Update the isLoading state and navigate to the home page.
62 setIsLoading(false);
63 router.push('/');
64 })
65 .catch((err) => {
66 // Alert the error and update the isLoading state.
67 alert(err);
68 setIsLoading(false);
69 });
70 };
71
72 return (
73 <div className={styles.CreatePage}>
74 <form onSubmit={handleSubmit}>
75 <h1>Create a new post</h1>
76 <div>
77 <label htmlFor="title">Title</label>
78 <input
79 id="title"
80 type="text"
81 value={formValues.title}
82 onChange={handleChange}
83 />
84 </div>
85 <div>
86 <label htmlFor="slug">Slug</label>
87 <input
88 id="slug"
89 type="text"
90 value={formValues.slug}
91 onChange={handleChange}
92 />
93 </div>
94 <div>
95 <label htmlFor="coverImage">Cover Image URL</label>
96 <input
97 id="coverImage"
98 type="text"
99 value={formValues.coverImage}
100 onChange={handleChange}
101 />
102 </div>
103 <div>
104 <label htmlFor="coverImageAlt">Cover Image Alt</label>
105 <input
106 id="coverImageAlt"
107 type="text"
108 value={formValues.coverImageAlt}
109 onChange={handleChange}
110 />
111 </div>
112 <div>
113 <label htmlFor="content">Content</label>
114 <textarea
115 id="content"
116 value={formValues.content}
117 onChange={handleChange}
118 />
119 </div>
120 <button type="submit" disabled={isLoading}>
121 {isLoading ? 'Creating...' : 'Create'}
122 </button>
123 </form>
124 </div>
125 );
126};
127
128export default CreatePage;
129
- Fill out the form with data such as this:
title
: My Third Blog Postslug
: my-third-blog-postcoverImage
: https://placekitten.com/g/700/300coverImageAlt
: A random kitten from PlaceKitten.com.content
: Check me out! I'm writing my third blog post on a site I created. I'm so proud of myself!
- Click the "Create" button.
- When it's finished loading, you should see the home page and your new post at the top of the list.
- Commit and push your work to your repository:
1git add .
2git commit -m "Adding createPost function and using it in CreatePage"
3git push
4
- Celebrate!!! You did it!!! 🎉
Don't forget that last time, we deployed our site to Vercel. With every git push
we've done, Vercel has re-built your site for you! It's already updated and you can share your progress with everyone you know. :)