In a recent post, I talked about some recent experimentation I did with GatsbyJS and the WordPress.com API. While it wasn’t quite a fit for rebuilding my personal site, I wanted to build something else with it. So, I elected to build an Instagram-esque photo sharing site. I thought it might be helpful to share a bit of the process so I’ll be walking through the following steps:
- Part 1: Getting Setup
- Part 2: Determining the Layout and Understanding Queries
- Part 3: Building a Header Component
- Part 4: Building a Grid Photo Layout
- Part 5: Building the Individual Photo (Coming Soon!)
- Part 6: Deploying to Netlify (Coming Soon!)
We’ll walk through each piece together from start to finish and get a live site working online.
The end result will look like this:
Part 1 – Getting Setup
First, I’m going to assume you have some tools installed already like Node, npm, and git. If you don’t have them installed already, I personally recommend using Homebrew to install them. Here’s a beginner-friendly post from Treehouse that will get you started.
Once you have the basics installed, we can install the Gatsby CLI tool using the following command:
npm install --global gatsby-cli
The Gatsby CLI tool will allow us to create new sites easily from the command line. Once that has finished, change into the directory where you want to store your code (I use a folder mysteriously titled “Code”) and run the following:
gatsby new photos
This creates a new Gatsby site titled “photos.” If you want to name your project folder something other than “photos” (maybe “photo site”), update the command to whatever you want to use. In my examples, I’ll use “photos-example” so I ran gatsby new photos-example
.
Now, let’s change into our directory and install some dependencies. You can do that by running:
cd photos
Replace ‘photos’ with whatever you decided to name your folder.
Installing Some Other Dependencies
There are a few other things we’ll need to install to make this work. Let’s just get them out of the way and install them right now. Inside your app directory, run the following:
npm install --save gatsby-source-wordpress
This installs the basic Gatsby plugin that allows you to source data from WordPress.com. You can read more about it here.
Finally, we need to install some image-related plugins for Gatsby. Install each of the following:
npm install --save gatsby-transformer-sharp
npm install --save gatsby-plugin-sharp
npm install --save gatsby-image
The command line will be your guide if you run into any issues.
Initialize a GitHub Repo
We’re going to be deploying to Netlify from GitHub so you need to setup a Git repository for your project. Go ahead and do that now. If you need help, checkout GitHub’s documentation.
Setup Your Gatsby Config File
The last step is to connect your local site with WordPress.com. To do that, we need to create an app on WordPress.com. Head over to: developer.wordpress.com/apps/. After you login, you’ll want to click “Create a new application.”
For our purposes, we don’t need to worry too much about the information. We won’t be authenticating any other users with our app. It’s just used to give our site access to WordPress.com. Here’s a snapshot of how I set it up:
After you create your app, you should have a client ID and client secret. The client secret will be a huge string of numbers.
Open up your new app in the text editor of your choice, and find the file titled gatsby-config.js
. It should look like this:
We’re going to add in our Gatsby Source WordPress plugin to pull content from WordPress.com using our app credentials. Here’s a gist of what the entire gatsby-config.js
file should look like when you’re done. You can just copy and paste the entire file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module.exports = { | |
siteMetadata: { | |
title: `Gatsby Default Starter`, | |
}, | |
plugins: [ | |
{ | |
resolve: 'gatsby-source-wordpress', | |
options: { | |
baseUrl: 'YOUR SITE URL', | |
protocol: 'https', | |
hostingWPCOM: true, | |
useACF: false, | |
auth: { | |
wpcom_app_clientSecret: 'CLIENT SECRET', | |
wpcom_app_clientId: 'CLIENT ID', | |
wpcom_user: 'USERNAME', | |
wpcom_pass: 'PASSWORD', | |
}, | |
verboseOutput: false, | |
}, | |
}, | |
] | |
} |
Some obvious bits you’ll want to replace:
- “Your Site URL” should be a WordPress.com site URL on your account like jeremeypt.wordpress.com.
- “Client Secret” and “Client ID” will be found from the app we created above.
- “Username” and “Password” are your WordPress.com username and password. If you don’t already, please be sure to use a password manager and all the necessary precautions.
Conclusion – Part 1
Once you have saved your gatsby-config.js
file, you can head on over to your terminal and run gatsby develop
. This will build your site and all necessary pages. If you check your terminal, you should also see that Gatsby is reaching out to WordPress.com and finding some pages and posts on whatever site you connected it to.
It might take a moment, but once it’s finished, it will direct you to http://localhost:8000/
in your browser. You should see the following:
Don’t worry. We’ll remove all of the default content and begin building our app in the next section. This should get us all setup though!
Part 2 – Determining the Layout and Understanding GraphQL Queries
Before we begin building, let’s take a look at a typical Instagram page. It will look something like this:
A few things stand out:
- We have a header component that should offer up a quick bio and photo. It should be present on all pages.
- Instagram has a neat little infinite scroll option. Initially, it only loads 12 photos. When you scroll down though and click on “Load More,” it flips into infinite scroll mode.
- The photos are laid out in a grid formation three photos across. One thing you’ll notice if you resize your browser or visit Instagram.com on your phone is that the photos are always in a three-across grid formation regardless of screen size. This is a bit different than what I previously expected (a grid that adjusts to one or two across on small screens).
If we were to draw out the anticipated layout then, we would get something like this:
We’re going to add in a quick footer with some attribution as well at the very end.
Sourcing Data Using GraphQL
The goal with this entire experiment is to use a WordPress.com site as a database of sorts for pulling down data. We want to source as much data as possible from WordPress.com instead of hardcoding it in our app. That way, we can update it directly from WordPress.com without touching a bit of code.
For example, we want the bio to come from the “About Me” section of your profile at wordpress.com/me. We don’t want to code it directly into the app making it difficult to change in the future.
Last time, we set up the Gatsby Source WordPress plugin and added in some configurations that will pull down data from a specific WordPress.com site. We didn’t talk at all about how to actually access that data and use it in our app.
GatsbyJS makes this relatively simple by using a tool called GraphQL. GraphQL is a consistent interface for interacting with your data regardless of the original source. In the particular use case of Gatsby, we can think of it like this:
- The Gatsby Source WordPress plugin pulls down all posts, pages, comments, etc for your site.
- Gatsby implements GraphQL to provide a consistent language for interacting with that data so you can pull in what you need for each page.
- You use GraphQL queries for each page to tell Gatsby the data you need for this particular page to render.
If you’re curious about how Gatsby uses GraphQL, I’d recommend checking out this page.
If you’re new to GraphQL, it can feel a bit foreign at first, but as we walk through building a few pages and components, you’ll get much more familiar with it. Just think of GraphQL as a specific language you can use to interact with and call up various parts of your data whenever you need it.
Let’s see how it might work. Change into the Gatsby directory we built in part 1 of this tutorial and run gatsby develop
. That will fire up our site. We can visit http://localhost:8000/
in our browser to see what our site looks like, but we can also test out something else. GraphQL provides an in-browser tool for testing out GraphQL queries. It’s called GraphiQL. Once your site builds, you can test it out by visiting http://localhost:8000/___graphql
. You should see this:
On the right-hand side, you’ll find a documentation explorer that will come in very handy down the road to find out the specific queries you need. On the left-hand side, you’ll see a query sandbox where you can enter GraphQL queries and see what data is returned. We won’t go into too much more about GraphQL or specific queries in this series, but just know that this playground exists if you ever run into trouble or find yourself wondering about a query.
For now, let’s see how we might return data using GraphQL. On the left-hand side of the screen, remove all of the base text and enter the following:
{ allWordpressPost { edges { node { title } } } }
Press the play button at the top to run your query. On the right-hand side, you should see a list of titles from your posts!
I’ll provide all the queries we’ll need for this series, but in short, allWordpressPost
(capital_P_dangit()
) is a built-in query that will look at all posts on the site you have connected. There are others too that we’ll explore like allWordpressWpMe
. You’ll notice if you try to type another query in the GraphQL IDE, it will attempt to autocomplete for you as well, which is handy.
Conclusion – Part 2
At this point, we have our designated layout, and we understand a bit more about how we’ll be using our data. The next step is to get building! In the next piece of this series, we’ll figure out how to determine the information we need for our header and build the component to display that information.
Part 3 – Building the Header
Our app is going to be very simple. We’re going to have a handful of components max and only two page templates – the home/grid page and a single photo layout. First, we need to remove some of the unnecessary data that’s present in the initial files leftover from when we first created our new Gatsby site. Your file structure looks a bit like this right know:
Under src/pages/
, delete the file titled page-2.js
. We’ll work with everything else.
To make everything easy to test and contained, we’re going to create a new folder labeled components
that will house the individual components we want to reuse throughout the app. For right now, create that file under src
so it should be inline with layouts
and pages
.
Then, once you have the folder created, go ahead and create a file titled header.js
under components
. When you’re done, the file structure should look a bit like this:
To explain some of these files a bit more, anything under the pages
folder will be an actual route on our site like /page-2 or something similar. The index.js
file under pages
serves up our homepage. The 404.js
file will (you probably guessed it) serve up a 404 page when we get this up on a server. The layouts
folder with index.js
holds a generic layout for the homepage.
If you load up the site right now by running gatsby develop
in your app directory, you won’t see much difference. Let’s get started on building the Header component though and see what we can come up with.
Building a Header Component
Since we’re mocking up the Instagram layout, we can take a quick peek at the information we’ll need.
We’ll need a username, an avatar, and a bio. That’s simple enough, and thankfully, it’s all available through the WordPress.com API.
In header.js
, let’s go ahead and create our new component. We’ll use a pure function. Then, we’ll use the PropTypes
declaration to identify the data we’ll need. Finally, we’ll return some HTML to see if this thing is actually on. You could have something like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react' | |
import proptypes from 'prop-types' | |
const Header = () => { | |
return ( | |
<h1>Sup</h1> | |
) | |
} | |
Header.PropTypes = { | |
src: proptypes.string, | |
username: proptypes.string, | |
bio: proptypes.string, | |
} | |
export default Header; |
It’s not doing much, but at least our Header
component should be returning an h1
now with some text. Let’s test it out. Save this file then head over to src/components/index.js
. Leave the parent div
in place under the IndexPage
but remove the extra default HTML added by Gatsby. Finally, import your new Header
component and render it to the page. Your src/components/index.js
file could look a bit like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// External | |
import React from 'react'; | |
import Link from 'gatsby-link'; | |
// Internal | |
import Header from '../components/header'; | |
const IndexPage = () => ( | |
<div> | |
<Header /> | |
</div> | |
) | |
export default IndexPage; |
Of note, you may be wondering why we had to use a return
statement in our Header
component, but you don’t see one in IndexPage
. Pro-tip: The usage of const MyComponent = () => ()
implies a return
statement whereas const MyComponent = () => {}
does not. I prefer the latter with an added return
just to make the return extra clear.
If you save the file once you’ve added the Header
component and refresh your page (it should refresh automatically on save), you’ll get something like this:
It’s not much, but at least we have something rendering to the page now!
Okay, let’s go back to our Header
component and flesh it out a bit more. As you probably guessed, I’ve already worked through the code required to make this header look like the header from Instagram. I’ll share some finished code along the way, but I would encourage you to experiment if you would like. Some things to keep in mind:
- GatsbyJS uses CSS-in-JS by default, which if you haven’t seen it before, can look a bit strange. When in doubt, camelcase your property and include quotes around your values. For example,
padding-bottom: 10px;
becomespaddingBottom: '10px',
. You’ll get the hang of it. - We want our `Header` component to be dumb and predictable. Meaning, we don’t want it to fetch data of any kind. We want to pass in data from a higher component. We’ll sketch out the `Header` component with some dummy data then find out how to replace it with actual data in a second.
Here’s the Header
component a bit more fleshed out with dummy data. It looks really long, but that’s mostly due to how I like to style my JavaScript. It’s basically a parent div
with an image, header, and some paragraph text. The username uses Gatsby’s Link
component to link back to the homepage so we have a way of navigating around.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import PropTypes from 'prop-types'; | |
import Link from 'gatsby-link'; | |
const Header = () => { | |
const bio = 'Lorem Ipsum is simply dummy text that I want to appear on the screen.'; | |
const username = 'username'; | |
const src = 'https://www.gravatar.com/avatar/00000000000000000000000000000000?d=retro'; | |
return ( | |
<div | |
style={ { | |
display: 'flex', | |
alignItems: 'center', | |
justifyContent: 'center', | |
flexWrap: 'wrap', | |
margin: '2em 2em', | |
} } | |
> | |
<span | |
style={ { | |
flexBasis: '120px', | |
height: '96px', | |
} } | |
> | |
<img | |
src={ src } | |
alt={ `Jeremey DuVall` } | |
style={ { | |
marginBottom: 0, | |
borderRadius: "50%", | |
width: '96px', | |
} } | |
/> | |
</span> | |
<span | |
style={ { | |
flexBasis: '500px', | |
flexGrow: 1, | |
} } | |
> | |
<Link | |
to='/' | |
activeStyle={ { | |
textDecoration: 'none', | |
color: '#000000' | |
} } | |
> | |
<h3 | |
dangerouslySetInnerHTML={ { __html: ( username ) } } | |
style={ { marginBottom: '0.2em' } } | |
/> | |
</Link> | |
<p | |
style={ { marginBottom: 0 } } | |
dangerouslySetInnerHTML={ { __html: ( bio ) } } | |
/> | |
</span> | |
</div> | |
) | |
} | |
Header.PropTypes = { | |
src: PropTypes.string, | |
username: PropTypes.string, | |
bio: PropTypes.string, | |
} | |
export default Header; |
I will say that Flexbox makes this entire thing much easier so if you haven’t already, be sure to check out a tutorial on Flexbox somewhere online.
Save that Header
file. If you flip over to your browser, you should see your header getting rendered to the page, which is awesome! However, you’re also going to see the default Gatsby header still. Let’s fix that.
Head over to src/layouts/index.js
. You’ll see a component called TemplateWrapper
and another header component that Gatsby includes by default. We can remove all of the default text. All that we need there is the following for right now:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import PropTypes from 'prop-types'; | |
import './index.css'; | |
const TemplateWrapper = ( { children } ) => ( | |
<div> | |
<div | |
style={ { | |
margin: '0 auto', | |
maxWidth: 960, | |
padding: '0px 1.0875rem 1.45rem', | |
paddingTop: 0, | |
} } | |
> | |
{ children() } | |
</div> | |
</div> | |
) | |
TemplateWrapper.propTypes = { | |
children: PropTypes.func, | |
} | |
export default TemplateWrapper |
Voila! You should now see something like this with a dummy Gravatar and our Lorem Ipsum text:
Import the Data From WordPress.com
Hard coding the data is fun, but it’s not practical. It would be really nice to pull the data directly from WordPress.com so we don’t have to open up the text editor any time we want to edit our bio.
As explained above, we want our Header
component to be dumb meaning we don’t want it to fetch data. Instead, it should just use the data we pass down to it.
We’ll use a GraphQL query in the IndexPage
to grab some data we’ve pulled down from WordPress.com. Again, if you haven’t used GraphQL before, it can look a bit different. If you need to test out any of the queries, I recommend starting at http://localhost:8000/___graphql, which is the link to your GraphQL IDE when your site is up and running. Here’s the query we’ll use:
export const pageQuery = graphql` query IndexQuery { allWordpressWpMe { edges { node { name description avatar_urls { wordpress_96 } } } } } `
This reaches out to allWordpressWpMe
, which is exposed as part of GatsbyJS. It grabs the name, description, and avatar source URL from the account you connected way back in part 1.
We’ll use it like this at the end of the file. This is what your src/pages/index.js
page should look like at this point:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// External | |
import React from 'react'; | |
import Link from 'gatsby-link'; | |
// Internal | |
import Header from '../components/header'; | |
const IndexPage = ( props ) => { | |
return ( | |
<div> | |
<Header /> | |
</div> | |
) | |
} | |
export default IndexPage | |
export const pageQuery = graphql` | |
query IndexQuery { | |
allWordpressWpMe { | |
edges { | |
node { | |
name | |
description | |
avatar_urls { | |
wordpress_96 | |
} | |
} | |
} | |
} | |
} | |
` |
You’ll notice I modified the main component a bit to include an explicit return statement.
Alright – we’re almost there! Now, if you drop a console.log( props )
before the return statement, you’ll find that we now have a data.allWordpressWpMe
object available. If you inspect it a bit more, you’ll find that it has all of the information we need. We just need to pass it down to the Header
component and tell the component to use the passed down data, not the dummy data. Here’s what both files will look like when you’re done.
src/pages/index.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// External | |
import React from 'react'; | |
import Link from 'gatsby-link'; | |
// Internal | |
import Header from '../components/header'; | |
const IndexPage = ( props ) => { | |
const bio = props.data.allWordpressWpMe.edges[0].node.description; | |
const username = props.data.allWordpressWpMe.edges[0].node.name; | |
const avatar = props.data.allWordpressWpMe.edges[0].node.avatar_urls.wordpress_96; | |
return ( | |
<div> | |
<Header bio={ bio } username={ username } src={ avatar } /> | |
</div> | |
) | |
} | |
export default IndexPage | |
export const pageQuery = graphql` | |
query IndexQuery { | |
allWordpressWpMe { | |
edges { | |
node { | |
name | |
description | |
avatar_urls { | |
wordpress_96 | |
} | |
} | |
} | |
} | |
} | |
` |
src/components/header.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import PropTypes from 'prop-types'; | |
import Link from 'gatsby-link'; | |
const Header = ( props ) => { | |
const bio = props.bio; | |
const username = props.username; | |
const src = props.src; | |
return ( | |
<div | |
style={ { | |
display: 'flex', | |
alignItems: 'center', | |
justifyContent: 'center', | |
flexWrap: 'wrap', | |
margin: '2em 2em', | |
} } | |
> | |
<span | |
style={ { | |
flexBasis: '120px', | |
height: '96px', | |
} } | |
> | |
<img | |
src={ src } | |
alt={ `Jeremey DuVall` } | |
style={ { | |
marginBottom: 0, | |
borderRadius: "50%", | |
width: '96px', | |
} } | |
/> | |
</span> | |
<span | |
style={ { | |
flexBasis: '500px', | |
flexGrow: 1, | |
} } | |
> | |
<Link | |
to='/' | |
activeStyle={ { | |
textDecoration: 'none', | |
color: '#000000' | |
} } | |
> | |
<h3 | |
dangerouslySetInnerHTML={ { __html: ( username ) } } | |
style={ { marginBottom: '0.2em' } } | |
/> | |
</Link> | |
<p | |
style={ { marginBottom: 0 } } | |
dangerouslySetInnerHTML={ { __html: ( bio ) } } | |
/> | |
</span> | |
</div> | |
) | |
} | |
Header.PropTypes = { | |
src: PropTypes.string, | |
username: PropTypes.string, | |
bio: PropTypes.string, | |
} | |
export default Header; |
Conclusion – Part 3
At long last, we should have our header in place with data pulled programmatically from WordPress.com!
Part 4 – Building a Grid Photo Layout
In this fourth part, we’ll put together the grid layout for the homepage. As we did last time with the Header
component, we obviously want to pull our photos from WordPress.com; that’s the entire purpose of building this fun little tool.
To do that, we’ll put together a GraphQL query that returns a list of posts on your site and their featured images. That’s how we’ll display the image. It will be the featured image of a specific post. If you don’t already have some posts on your site with featured images, go ahead and update that through WordPress.com.
Once you have done that, let’s head over to the GraphiQL IDE (http://localhost:8000/___graphql). Enter the following query:
allWordpressPost( sort: { fields: [ date ], order: DESC } ) edges { node { id title slug date( formatString: "/YYYY/MM/DD/" ) featured_media { localFile { childImageSharp { id } } } } } }
This looks at all the WordPress posts returned from your site. It filters them by date in a descending order (so the most recent is at the top). Then, for each node it finds, it returns the id, title, slug, date in a specified format, and featured media file. This highlights a few fun facts about Gatsby/GraphQL:
- With GraphQL, you can tell it the format you want the date returned in.
- Gatsby automatically imports and optimizes the featured image for you, which is why we can use `localFile` on the `featured_media` property. We’ll see more of how this works in a second.
If you run the query, you should get something that looks like this:
Now, let’s combine that with our other GraphQL query we used for the Header
component on src/pages/index.js
. The resulting query should look like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export const pageQuery = graphql` | |
query IndexQuery { | |
allWordpressWpMe { | |
edges { | |
node { | |
name | |
description | |
avatar_urls { | |
wordpress_96 | |
} | |
} | |
} | |
} | |
allWordpressPost( sort: { fields: [ date ], order: DESC } ) { | |
edges { | |
node { | |
id | |
title | |
slug | |
date( formatString: "/YYYY/MM/DD/" ) | |
featured_media { | |
localFile { | |
childImageSharp { | |
sizes( maxWidth: 1000 ) { | |
…GatsbyImageSharpSizes | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
` |
The only major difference between this query and the one we’re using on the page is the use of childImageSharp
and the fragment ...GatsbyImageSharpSizes
. This has to do with how Gatsby processes images. If you’re curious to learn more, I would encourage you to read this page.
Now, if we save this file and console.log( props )
, we’ll find that we have data.allWordpressPost
available to use!
Building the Photo and Photo Row Components
As I mentioned previously, the Instagram layout isn’t what I would have expected on small screens. On a typical desktop, Instagram has three photos to a row. When you shrink down to a small screen though, it still has three photos in a row instead of reorganizing to feature on larger photo at a time.
To recreate that layout, we’re going to create two “dumb” components. One will be an individual photo. The other will be a photo row that will hold three photos across.
Within our components
folder, create two new files – row.js
and photo.js
. Here are some full gists with the code that should go inside:
Photo.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// External | |
import React from 'react'; | |
import PropTypes from 'prop-types'; | |
import Img from 'gatsby-image'; | |
import Link from 'gatsby-link'; | |
const Photo = ( props ) => { | |
return ( | |
<div | |
style={ { | |
width: 'calc( 100% / 3 )', | |
padding: '0.2em', | |
} } | |
> | |
<Link to={ props.link } > | |
<Img | |
src={ props.src } | |
sizes={ props.sizes } | |
style={ { | |
width: 'auto', | |
height: 'auto', | |
} } | |
/> | |
</Link> | |
</div> | |
) | |
} | |
Photo.PropTypes = { | |
src: PropTypes.string, | |
sizes: PropTypes.object, | |
link: PropTypes.string, | |
} | |
export default Photo; |
Row.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// External | |
import React from 'react'; | |
import PropTypes from 'prop-types'; | |
// Internal | |
import Photo from './photo'; | |
const PhotoRow = ( props ) => { | |
return ( | |
<div | |
style={ { | |
display: 'flex', | |
flexDirection: 'row', | |
width: '100%', | |
} } | |
> | |
{ | |
props.photos.map( photo => { | |
return ( | |
<Photo | |
src={ photo.node.featured_media.localFile.childImageSharp.sizes.src } | |
sizes={ photo.node.featured_media.localFile.childImageSharp.sizes } | |
key={ photo.node.id } | |
link={ photo.node.date + photo.node.slug } | |
/> | |
) | |
} ) | |
} | |
</div> | |
) | |
} | |
PhotoRow.PropTypes = { | |
photos: PropTypes.array, | |
} | |
export default PhotoRow; |
You’ll notice a few things:
- Neither component is querying data. They just expect certain things.
- row.js expects an array of photos to be provided. It then uses the Photo component to return something.
- Photo.js expects a src string, sizes object, and link string. It then uses the built-in Gatsby Img component along with the Link component we used previously.
If you refresh the page at this time, you won’t see much. Let’s head back over to our src/pages/index.js
file to bring this all together.
Outputting Photos on the Homepage
Import the Row
component into src/pages/index.js
. At this point, we have an array of posts provided by allWordpressPost
. We also have a Row
component that is designed to output the photo. What we need to do is loop over the posts array and return every three items. If we’re at the end of the array and we have a straggler (one or two photos left over), those should be returned in their own array.
We can do that with the following two functions:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const photos = props.data.allWordpressPost.edges; | |
const displayPhotos = () => { | |
const photoArray = []; | |
let photoRow = []; | |
let count = 0; | |
photos.map( photo => { | |
if ( photo.node.featured_media ) { | |
photoArray.push( photo ); | |
} | |
} ); | |
return ( | |
photoArray.map( photo => { | |
if ( photoRow.length === 3 ) { | |
photoRow = []; | |
} | |
photoRow.push( photo ); | |
count++; | |
if ( photoRow.length === 3 ) { | |
return returnRow( photoRow, count ); | |
} else if ( photoArray.length – count === 0 ) { | |
return returnRow( photoRow, count ); | |
} | |
} ) | |
) | |
} | |
const returnRow = ( photos, count ) => { | |
return ( | |
<PhotoRow photos={ photos } key={ count } /> | |
) | |
} |
The first function displayPhotos()
is the primary function we’ll call somewhere in our code to get some photos. It loops over the posts (which is assigned to a variable called photos
at the very top). Then, if a featured_media
property exists, it pushes the post onto an array called photoArray
. This check is important because we want to make sure the photo actually has a featured image we can use.
Then, the function maps over the new photoArray
and keeps a count. Whenever the count reaches three, it returns a photoRow
with the photos and resets the count. At the end, it returns the remainder if photoArray.length - count === 0
.
Finally, call the displayPhotos()
function in our return statement like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
return ( | |
<div> | |
<Header bio={ bio } username={ username } src={ avatar } /> | |
{ displayPhotos() } | |
</div> | |
) |
Now, save and refresh the page. You should end up with a grid layout like this:
If you scale down to small screens, you’ll notice that it adjusts just like the Instagram layout staying in a three-photo row the entire time.
Conclusion – Part 4
Right now, if you click on a photo, you’ll notice it takes you to our 404 page. In the next part, we’ll build the individual photo pages so the links work correctly.
Part 5 – Building the Individual Photo Page
Right now, our front page grid is looking great, but if you click on an individual photo, nothing happens. We need to do two things to fix this.
First, we need to create a template for individual photo pages. That way, GatsbyJS will know what content to display on individual routes. Second, we need to edit the gatsby-node.js
file to actually create the routes for the individual posts. We’re going to tackle them in reverse order building the routes first.
Building Individual Routes
GatsbyJS has a built-in createPage
function that you can call to build individual pages (like /example-post) whenever you run gatsby develop
or gatsby build
. When you run those commands, Gatsby looks through the gatsby-node.js
file and builds the necessary HTML for any pages on your site. That way, we can render those individual pages. Let’s walk through how we would edit our gatsby-node.js
file for building an individual post page.
For right now, we’re going to build a generic template to make sure this is all working correctly. Create a new folder called ‘templates’ that will live at src/templates
. In that new folder, create a single file titled single-photo.js
. The templates folder will represent just that – templates we’ll use to build individual pages on our site. Within single-photo.js
drop this code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// External | |
import React from 'react'; | |
import PropTypes from 'prop-types'; | |
// Internal | |
import Header from '../components/header'; | |
const SinglePhoto = ( props ) => { | |
const bio = props.data.allWordpressWpMe.edges[0].node.description; | |
const username = props.data.allWordpressWpMe.edges[0].node.name; | |
const avatar = props.data.allWordpressWpMe.edges[0].node.avatar_urls.wordpress_96; | |
return ( | |
<div> | |
<Header bio={ bio } src={ avatar } username={ username } /> | |
<div>hi</div> | |
</div> | |
) | |
} | |
export default SinglePhoto; | |
SinglePhoto.PropTypes = { | |
data: PropTypes.object | |
} | |
export const pageQuery = graphql` | |
query PhotoQuery { | |
allWordpressWpMe { | |
edges { | |
node { | |
name | |
description | |
avatar_urls { | |
wordpress_96 | |
} | |
} | |
} | |
} | |
} | |
` |
This is a generic layout that will get us started. It just renders our header to the page with some data queried from GraphQL. Then, it gives us a div with “hi” inside so we can make sure we’re rendering something to the page.
Now, let’s head on over to the gatsby-node.js
file, which should be in the root directory for your app. Right now, it should be blank, but we’re going to drop in this code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const _ = require(`lodash`) | |
const Promise = require(`bluebird`) | |
const path = require(`path`) | |
const slash = require(`slash`) | |
exports.createPages = ( { graphql, boundActionCreators } ) => { | |
const { createPage } = boundActionCreators; | |
return new Promise( ( resolve, reject ) => { | |
graphql( | |
` | |
{ | |
allWordpressPost { | |
edges { | |
node { | |
date( formatString: "YYYY/MM/DD/" ) | |
slug | |
} | |
} | |
} | |
} | |
` | |
).then( result => { | |
if (result.errors) { | |
console.log(result.errors) | |
reject(result.errors) | |
} | |
const template = path.resolve(`./src/templates/single-photo.js`); | |
_.each( result.data.allWordpressPost.edges, edge => { | |
createPage( { | |
path: edge.node.date + edge.node.slug, | |
component: slash( template ), | |
context: { | |
slug: edge.node.slug | |
}, | |
} ) | |
} ) | |
} ) | |
resolve(); | |
} ) | |
} |
Alright – let’s walk through what this code is doing step-by-step:
- First, it’s creating a promise. In JavaScript, a promise is like a contract. We’re going to wait until an operation finishes and then do something with the results. In this case, we’re going to use allWordpressPost to query the individual posts from our site including the date and slug for each post.
- Next, if it hits some errors, we’re going to log those out.
- Otherwise, if it doesn’t hit any errors, it looks at the path to our new single photo template and stores that in a variable called template. Then, it uses the built-in createPages function to generate a new page using that template. It creates a path of date + slug (i.e. 2017/12/11/this-post) to match the default WordPress.com permalink structure. Finally, it gives the createPage function a bit of context. We’ll find out why that’s important here in a bit.
Now, stop your app and restart it with the gatsby develop
command. When it builds, try visiting a route that you know doesn’t exist like http://localhost:8000/this-cant-be-a-route. You’ll hit the 404 page that’s automatically generated. More importantly though, you’ll see a list of all pages on your site. If everything goes according to plan, you should see all of the pages for our posts like this!
Even better, if you click on a page, you should see our header with “hi” printed to the page. It works!