visit
This article is part two in a weekly series on building a blog with React and is an extension of the code created in part one.
Part Two: Building a Blog with React and ContentfulPart Five: Replacing Redux Thunks with Redux SagasPart Six: (In Progress) Writing Unit Test for a React Blog with Redux Sagas
This will go over the basics of setting up Contentful and displaying its data as a blog feed in our React application.This does not include Redux… yet ;)
Contentful is very clean, easy to navigate, and it looks and feels a lot like Wordpress. As much as hate to admit, I love it and thats an amazing thing because I loth Wordpress. To be fair, I don’t hate Wordpress for its UX, I hate it because its slow and built on what looks like hacked together PHP, and lets face it, no one actually likes PHP. Not even the creator of PHP.
Contentful is content management as a service (CaaS), this replaces the need to have to connect your site to that terrible MySQL database hosted on some crappy Apachy Server, where you have to install phpMyAdmin and spend hours trying to figure out cPanel.
There are two parts to your data, the structure and the data. It’s basically a really nice web form the converts to simple json. You have to first set up your form the in Content Model, then you can hop over to Content and fill out your forms, each form becomes an item in the models array of data sent via rest end points.
The JSON preview will look like this
Now let’s create a Blog post for the app to pull and display on our Blog page. Go to Content, and Click Add Blog in the top corner and fill out the form.
Now you have your first blog post!
You will need a Space Id and access token to retrieve your data. Go to the APIs tab and click Add Api Key.
Name it what ever you want and save.
On src/index.js
let’s import Contentful, set up a simple client and then getEntries. Now let’s console log what we get, with any luck we should be see our test blog post. This is temporary.
import React from 'react';import ReactDOM from 'react-dom'import registerServiceWorker from './registerServiceWorker'import { BrowserRouter as Router } from 'react-router-dom'import * as contentful from 'contentful'import App from './App'import './index.css'
var client = contentful.createClient({space: 'qu10m4oq2u62',accessToken: 'f4a9f68de290d53552b107eb503f3a073bc4c632f5bdd50efacc61498a0c592a' })
client.getEntries().then(entries => {entries.items.forEach(entry => {if(entry.fields) {console.log(entry.fields)}})})
Isn’t that amazing. It was like, the simplest thing to set up ever! But now let’s do it right… well, as much as we can without Redux :P
Head over to your Blog.js
and change it from a dumb component to a smart React.Component class.
Next, using the React lifecycle hook componentDidMount, we need to call a fetchPosts function which will call a setPosts function
If you plug a console log in the fetchPosts function you will see we are getting our data back from Contentful.
Now that we are getting data and we are doing it the React way (w/o Redux), let’s update the render function to display the data. We will move these to their own pretty component later, but for now let’s stick it in some pre tags.
Head back over to Contentful and click Content > Add Blog, then fill out the information. You know the drill.
Click Publish and head get back over to your app, refresh the browser and BLAMMO!! There’s your second post.
We need to set up a Blog Item component. Our Blog page will contain a list of Blog Items which make up “the feed”.
and BlogItem.js
will be a child of Blog.js
. Since Blog.js
is in the app folder, we will create a folder called blog in the app folder, then create our BlogItem.js
in our newly created blog folder.
In BlogItem.js
, create as simple component with Bulma box. We’ll pass in props which for now will be a spread of the fields from Contentful.
Now, back in Blog.js we need update our posts map in render to return our BlogItem component and spread the fields as properties.
render() {return (<div><p>This is the Blog Page</p><br/>{ this.state.posts.map(({fields}, i) =><BlogItem key={i} {...fields} />)}</div>)}
Refresh and there you go. But… that kind of looks terrible.
Let’s replace <p>This is the Blog Page</p>
with a Bulma Hero for our page header. Since we will probably do this on the top of most of our pages, lets create this as a global reusable component.
Spoiler alert: If you haven’t figured it out yet,
App.js
and it’s folder of children (app
), will contain all our presentation logic. For now, every we do will be a child of App. , App will get a sibling namedStore.js
and its folder of children (store
) and it will contain all of our data services and state.
In the components folder create two files, PageHeader.js
and PageContent.js
PageHeader will be a component for our Bulma Hero. We will not be including the subtitle content. This way we can do css styling or include links and pass it as a children prop.
import React from 'react'
And in our Blog.js
replace <p>This is the Blog Page</p>
with
PageContent will be a styled component to confine our page to a max width and add padding and what have you.
import styled from 'styled-components'
Now hop over to your First and Second blog post and fill in some data.
Icon can be a url link to an icon or base64 as long is it can normally go into a src attribute.
Content should set as Markdown so when you fill your page content it can be styled with Markdown!
Do something like this for both posts, save, then refresh your. It probably going to look terrible…
YUP!
In the BlogItem.js
, replace content <p> with the react-markdown.
import React from 'react'import * as Markdown from 'react-markdown'
const BlogItem = (props) => (<div className="box content"><h1>{props.title}</h1><Markdown source={props.content} /></div>)
export default BlogItem BOOM!Much better, but I would remove the H1 titles from the Markdown in Contentful and rename your Contentful blog posts with what the H1 title was. You will see why when we add the Icon and stuff in next.
This is just simple Bulma. Open your BlogItem.js
and we are going to steal the box media example from Bulma’s website.
<h1>{props.title}</h1>
<Markdown source={props.content.split(" ").splice(0,150).join(" ").concat('...')} />
</div>
<div className="level">
<div className="level-left">
<Link className="level-item button is-small is-link is-outlined" to={props.path}>Read More</Link>
</div>
<div className="level-right">
<p className="level-item has-text-link is-size-7">
{moment(props.date).calendar(null, {
sameDay: '\[Today\]',
lastDay: '\[Yesterday\]',
lastWeek: '\[Last\] dddd',
sameElse: 'MMM Do YYYY'
})}
</p>
</div>
</div>
</div>
Hmm… If you remember correctly in our last article, Routing works off a static <Switch>
component. But we don’t want to have to hard code our every single blog post, that kind of defeats the purpose of using Contentful as CMS.
Luckily for us, react-router-dom has planned ahead and allows us to have path params. In Router.js
add new route
<Switch><Route exact path='/' component={Home}/><Route exact path='/blog' component={Blog}/><Route path='/blog/:blogPost' component={BlogPost}/></Switch>
Important: This one took me a hot minute to figure our but you CAN NOT include a dash (-) in the parameter name. I originally had
_:blog-post_
and couldn’t figure out why it looked like it was working but it wasn’t.
It’s also important to note that you need to add exact in the Blog path, otherwise it will match the
_/blog_
route first.
Update the <Link …>
in our BlogItem.js
to put the /blog/ in front of our path
<Link className="level-item button is-small is-link is-outlined"to={{pathname: `/blog/${props.path}`,state: { props }}}>Read More</Link>
But before we can test this, we need to create a BlogPost page. For now lets just do some simple <pre> displaying so we can make sure we are sending the data to the blog post as state import React from 'react'
Now when we navigate to our blog post from the blog page we should see a huge object in our <pre>
and the history object contains our state which is our blog post.
Now we can look at our BlogItem.js
and BlogPost.js
and find out overlapping code and make them nice pretty little components.
Let’s first create a folder for our new components. These are share components of the Blog so in the blog folder create a shared
folder, then create two files BlogContent.js
and BlogNav.js
In BlogContent.js
, we won’t always use **props.children**
but we want to put it there so we can place the nav in the that location on BlogItems.js
. You’ll see…
Now, lets update our BlogItem.js
and BlogPost.js
. This is gonna blow your mind.
andNotice how we are wrapping the BlogNav in this snippet, but not in the next one. This is what the props.children is used for.
That’s SO much better!
You don’t know this yet but I do plan to use this in other places so we are going to add this in the src/components
folder. Create a file called StatusTag.js
.
And back in our BlogNav.js
let’s add the StatusTag next to the date. And don’t forget to import it.
import StatusTag from './../../components/StatusTag'
const BlogNav = ({ to, date, status }) => (<nav className="level"><div className="level-left"><Link className="level-item button is-small is-link is-outlined" to={to}>Back to Blog</Link></div><div className="level-right"><StatusTag status={status} /><p className="level-item has-text-link is-size-7">{moment(date).calendar(null, {sameDay: '[Today]',lastDay: '[Yesterday]',lastWeek: '[Last] dddd',sameElse: 'MMM Do YYYY'})}</p></div></nav>)
And we need to update places we use the <BlogNav … >
component.
<BlogNav date={props.date} status={props.status} to="/blog" />
Now we have a nice little tag so we can let our readers know that we are still working on this article.Next —