Build a React & Firebase Blog Site Part 2
Published Tuesday, August 27, 2019 — 11 minute read
Welcome back! This is the second in a series of posts that will teach you how to build a blog site using React and Firebase. If you haven't read the first post in the series, I encourage you to do so. I'm including starter code for this post, but you'll additionally need to go through the following steps from the previous post to use it:
- Step 1: Sign up for Firebase (if you don't have an account already)
- Step 2: Create a project
- Step 5: Add Data to the Database
A fair warning: since this post is building off of Part 1, it assumes you have read the post if you're closely following along.
1. Clone the (Part 2) Starter Code [Optional]
Skip this step if you've successfully completed Part 1. Otherwise, you can start clone the code by running the following command in a terminal:
1git clone https://github.com/ashleemboyer/react-firebase-blog-starter-part-2.git
2
You'll need to change one file before continuing: src/firebase.js
. If you open it, you'll see the following config
constant:
1const config = {
2 apiKey: "<YOUR-API-KEY>",
3 authDomain: "<YOUR-AUTH-DOMAIN>",
4 databaseURL: "<YOUR-DATABASE-URL>",
5 projectId: "<YOUR-PROJECT-ID>",
6 storageBucket: "<YOUR-STORAGE-BUCKET>",
7 messagingSenderId: "<YOUR-MESSAGE-SENDER-ID>",
8 appId: "<YOUR-APP-ID>"
9};
10
The attributes within the constant are used to connect your app to your Firebase project. To find these values, go to your project settings using through the gear icon in the left sidebar of the Firebase console. Scroll down to the "Firebase SDK snippet" under "Your apps" and copy the attributes from what they're calling firebaseConfig
. Replace the attributes in your config
constant with these values.
Now you can run npm install
and then npm run start
to see your project in a browser.
2. Add a Page for Creating Posts
To get started on implementing a "Create Post" feature, let's first make a page we'll use to create posts. Add a create.js
file in src/pages
. We're going to keep it really simple for now to make sure everything is still working.
Developing code is most easy when you take incremental steps and check your work along the way.
Our Create
component will give a friendly, "Hello," for now.
1import React from "react";
2
3const Create = () => {
4 return <h1>Hello, from Create!</h1>;
5};
6
7export default Create;
8
Try navigating to the /create
page. Uh, oh... the page isn't found?
No worries! We just need to add a Route
for our new page in src/App.js
. First add an import for the Create
component:
1import Create from "./pages/create";
2
Then, add the following Route
below the one for the /
path:
1<Route path="/create" component={Create} />
2
The new create page should be working now!
3. Make Inputs for Describing Posts
Now, let's think about the data structure we have in our Realtime Database. Each of the following are attributes we use to describe a blog post:
- title
- slug
- date
- coverImage
- coverImageAlt
- content
Consider these two questions: Which of these attributes do we need to create an <input>
for? Which ones can we automatically generate with some additional code? Well, ...
If I were making this blog site for a client, I would auto-generate the slug
and the date
. Dates aren't that hard to auto-generate, but slugs can be because of punctionation. We're not going to handle that here, but feel free to give it a try on your own! In addition, I'd likely provide a file upload input for coverImage
, but that's also a little more complex than I'd like to take this post.
So, date
is the only thing we're going to auto-generate. We'll have inputs for everything except content
, which will get a <textarea>
. Let's add all of those to our component and handle their values with useState
. Your src/pages/create.js
file should look like this:
If this seems like a lot of redundant code to you, good catch! (And I'm sorry. 😅) We'll fix that in a "going further" post.
1import React, { useState } from "react";
2
3const labelStyles = {
4 display: "block",
5 marginBottom: 4
6};
7
8const inputStyles = {
9 width: "100%",
10 height: "2rem",
11 lineHeight: "2rem",
12 verticalAlign: "middle",
13 fontSize: "1rem",
14 marginBottom: "1.5rem",
15 padding: "0 0.25rem"
16};
17
18const Create = () => {
19 const [title, setTitle] = useState("");
20 const [slug, setSlug] = useState("");
21 const [coverImage, setCoverImage] = useState("");
22 const [coverImageAlt, setCoverImageAlt] = useState("");
23 const [content, setContent] = useState("");
24
25 const createPost = () => {
26 console.log({ title, slug, coverImage, coverImageAlt, content });
27 };
28
29 return (
30 <>
31 <h1>Create a new post</h1>
32 <section style={{ margin: "2rem 0" }}>
33 <label style={labelStyles} htmlFor="title-field">
34 Title
35 </label>
36 <input
37 style={inputStyles}
38 id="title-field"
39 type="text"
40 value={title}
41 onChange={({ target: { value } }) => {
42 setTitle(value);
43 }}
44 />
45
46 <label style={labelStyles} htmlFor="slug-field">
47 Slug
48 </label>
49 <input
50 style={inputStyles}
51 id="slug-field"
52 type="text"
53 value={slug}
54 onChange={({ target: { value } }) => {
55 setSlug(value);
56 }}
57 />
58
59 <label style={labelStyles} htmlFor="cover-image-field">
60 Cover image
61 </label>
62 <input
63 style={inputStyles}
64 id="cover-image-field"
65 type="text"
66 value={coverImage}
67 onChange={({ target: { value } }) => {
68 setCoverImage(value);
69 }}
70 />
71
72 <label style={labelStyles} htmlFor="cover-image-alt-field">
73 Cover image alt
74 </label>
75 <input
76 style={inputStyles}
77 id="cover-image-alt-field"
78 type="text"
79 value={coverImageAlt}
80 onChange={({ target: { value } }) => {
81 setCoverImageAlt(value);
82 }}
83 />
84
85 <label style={labelStyles} htmlFor="content-field">
86 Content
87 </label>
88 <textarea
89 style={{ ...inputStyles, height: 200, verticalAlign: "top" }}
90 id="content"
91 type="text"
92 value={content}
93 onChange={({ target: { value } }) => {
94 setContent(value);
95 }}
96 />
97 <div style={{ textAlign: "right" }}>
98 <button
99 style={{
100 border: "none",
101 color: "#fff",
102 backgroundColor: "#039be5",
103 borderRadius: "4px",
104 padding: "8px 12px",
105 fontSize: "0.9rem"
106 }}
107 onClick={createPost}
108 >
109 Create
110 </button>
111 </div>
112 </section>
113 </>
114 );
115};
116
117export default Create;
118
Here's what your page should look like now:
Briefly fill in all of these fields and see what happens when you click the "Create" button! Is your console open? Then you should see an object printed with the values of all your inputs. Here's what mine looks like (click to expand):
4. Write a Create Function
Neat! This is also what I mean by incremental changes to code. Take it step by step. Let's get to the exciting part! We're going to send some stuff back to the database. First, we need to import our getFirebase
function from src/firebase.js
.
1import { getFirebase } from "../firebase";
2
Now, add the generateDate()
function, update the createPost
function, and destructure the history
prop from the Create
component params:
1const generateDate = () => {
2 const now = new Date();
3 const options = { month: "long", day: "numeric", year: "numeric" };
4
5 const year = now.getFullYear();
6
7 let month = now.getMonth() + 1;
8 if (month < 10) {
9 month = `0${month}`; // prepend with a 0
10 }
11
12 const day = now.getDate();
13 if (day < 10) {
14 day = `0${day}`; // prepend with a 0
15 }
16
17 return {
18 formatted: `${year}-${month}-${day}`, // used for sorting
19 pretty: now.toLocaleDateString("en-US", options) // used for displaying
20 };
21};
22
1const createPost = () => {
2 const date = generateDate();
3 const newPost = {
4 title,
5 dateFormatted: date.formatted,
6 datePretty: date.pretty,
7 slug,
8 coverImage,
9 coverImageAlt,
10 content
11 };
12 getFirebase()
13 .database()
14 .ref()
15 .child(`posts/${slug}`)
16 .set(newPost)
17 .then(() => history.push(`/`));
18};
19
1const Create = ({ history }) => {
2 ...
3}
4
Fill in your inputs like we did a few minutes ago, click "Create post", and you should be brought to the home page with your new post at the very top!
5. Wrapping Up
Whew! I thought we'd be able to talk about all 4 CRUD functions in one post, but that was a lot. We'll continue on this adventure tomorrow. Great job so far. 💪
If come across any issues or questions, send me an email or DM me on Twitter! Catch you again tomorrow. 😊