Skip Navigation
Go to home page

Build a Blog Site with Next.js and Firebase Part 2 - Creating New Posts

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!


Add a Page for Creating Posts

First, let's add styles and a minimal component for our new CreatePage component.

  1. Add create.module.scss in the styles 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
  1. Add create.js in the pages 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
  1. 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:

A white webpage that reads "Hello from CreatePage!" in black text.

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

  1. 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
  1. Add some event handler functions and a <form> with the <input>, <textarea>, and <button> elements we need to CreatePage:
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
  1. Enter some data into each of the controls. Here's a list of what I'm entering as an example:
  • title: My Third Blog Post
  • slug: my-third-blog-post
  • coverImage: https://placekitten.com/g/700/300
  • coverImageAlt: 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!
  1. Click the "Create" button.
  2. Check the dev tools console.
  3. Your page and console should look like this:

Window showing the create page and inputs on the left and the dev tools console on the right. The console shows an expanded JSON object with the form values correctly stored.

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

  1. Add a createPost function to lib/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
  1. 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 from next/router after the useState import.
  • Import createPost from @lib/firebase after the useRouter import.
  • Instantiate the router at the top of the CreatePage function.
  • Add an isLoading state after the formValues instantiation.
  • Update handleSubmit to check for missing values, set the isLoading state, and call createPost.
  • Update the submit <button> to use isLoading.
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
  1. Fill out the form with data such as this:
  • title: My Third Blog Post
  • slug: my-third-blog-post
  • coverImage: https://placekitten.com/g/700/300
  • coverImageAlt: 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!
  1. Click the "Create" button.
  2. When it's finished loading, you should see the home page and your new post at the top of the list.

Window showing the home page and list of blog posts.

  1. Commit and push your work to your repository:
1git add .
2git commit -m "Adding createPost function and using it in CreatePage"
3git push
4
  1. 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. :)

Back to Top