Use Javascript's Fetch API with async/await to fetch your Instagram feed in React

Search for a command to run...

In late August of 2018, it was announced that the site was going to begin displaying advertisements on user profiles. Since Instagram's popularity increased, it became clear that there was another thing that the site needed: more engagement. Since users are able to upload pictures and content that others can comment on, it was only a matter of time before another option for companies came into play: selling engagement and sponsored views. This means that instead of having users upload pictures that only give a small glimpse of what the business offers, they can now buy instagram likes so that they can show a bigger picture, in the form of an ad. Of course, the ad is still a picture, but it is one that are more intimate and allow the user more interaction with the company, something that they might not be able to achieve with just a regular picture.
A live output goes well for this post
You can find the demo here (https://gatsby-instagram-feed.netlify.app/) and repo here (https://github.com/LarsBehrenberg/gatsby-instagram-feed)
Demo (here) | Repo (here) With the rise of the JAMStack and the serverless approach, most of the things that used to require a backend are now handled completely on the front-end or through APIs. But sometimes this is not enough, and that's where "se...

Google Fonts are amazing and widely popular on the Internet. Its API makes up 89.28% of the web fonts market share (see source). However, I often noticed that many websites struggle with fetching their fonts and end up render-blocking their website b...

Writing CSS is hard. Even though it often is made fun of and not seen as a programming language, writing consistent and pretty CSS is really hard. And in my journey as a self-taught web developer, I have often noticed that online classes and tutorial...

One of the reasons why Gatsby JS has been so popular over the years is its vast plugin library that makes it almost as easy as drag-and-drop to add new functionality to your app. Because of that, Gatsby has managed to make adding a headless CMS a st...

I have been a freelance web developer for a while now, but in the beginning, I was always wondering whether WordPress development is going to be the only way to create websites for my clients? I was looking for something to take advantage of my JavaS...

<Lars' Blog />
15 posts
Hi there, I am a freelance web developer who loves building things and blogs about the process of it. Feel free to reach out to me via my email or on Twitter!
Especially for blogs these days, Instagram feed implementations have become more and more popular. In Gatsby JS case, I have often used this handy plugin called gatsby-source-instagram, but as new images are only loaded during build time and not on page load, I have recently gone over to use a more general approach using JavaScripts Fetch API with Async/Await.
Let's get into it!
*Quick note: Here is the repo and the demo.
Now before we get right into, this is what our bare React Component will look like:
import React from "react"
export default class Instagram extends React.Component {
state = { photos: [], loading: true }
// Your Instragam ID can be retrieved here. Just make sure to replace your instagram name at the end
// https://www.instagram.com/web/search/topsearch/?context=blended&query=INSTAGRAM_USERNAME
// Your specifications needed for the fetch call later
INSTAGRAM_ID = "787132"
THUMBNAIL_WIDTH = 640
PHOTO_COUNT = 30
async componentDidMount() {
// later fetch our posts here
}
render() {
return (
<div className="post-wrapper">
{/* map through our posts here */}
</div>
)
}
}
Two things are important to note here!
Our state is storing an empty array which we will later use to store our images once loaded and secondly, a loading boolean which we will use to show a loading animation.
The variables below are specifications for our fetch call later. By switching out the username at the end, you can use this URL:
https://www.instagram.com/web/search/topsearch/context=blended&query=INSTAGRAM_USERNAME
to make a GET request to the Instagram API and get your specific ID for your account. Once that's done, you choose your thumbnail width needed and the number of images you would like to load.
Thumbnails are available with these sizes:
Async/Await allows us to asynchronously load our page while we are fetching our posts in our componentDidMount function. This looks like so:
import React from "react"
export default class Instagram extends React.Component {
state = { photos: [], loading: true }
// Your specifications
INSTAGRAM_ID = "787132"
THUMBNAIL_WIDTH = 640
PHOTO_COUNT = 30
async componentDidMount() {
try {
// Hack from https://stackoverflow.com/a/47243409/2217533
const response = await fetch(
`https://www.instagram.com/graphql/query?query_id=17888483320059182&variables={"id":"${this.INSTAGRAM_ID}","first":${this.PHOTO_COUNT},"after":null}`
)
const { data } = await response.json()
const photos = data.user.edge_owner_to_timeline_media.edges.map(
({ node }) => {
const { id } = node
const comments = node.edge_media_to_comment.count
const likes = node.edge_media_preview_like.count
const caption = node.edge_media_to_caption.edges[0].node.text
const thumbnail = node.thumbnail_resources.find(
thumbnail => thumbnail.config_width === this.THUMBNAIL_WIDTH
)
const { src, config_width: width, config_height: height } = thumbnail
const url = `https://www.instagram.com/p/${node.shortcode}`
return {
id,
caption,
src,
width,
height,
url,
comments,
likes,
}
}
)
this.setState({ photos, loading: false })
} catch (error) {
console.error(error)
}
}
render() {
return (
<div className="post-wrapper">
{/* map through our posts here */}
</div>
)
}
}
To use our try/catch block for our API GET call, we have to make our componentDidMount function asynchronous. Then we can use the fetch API with the await keyword to call the API and get the data to our specifications. All that is left, is to de-structure the variables that we need into the photos array in our state.
Now that we have received and stored our posts, we can go ahead and map through them.
import React from "react"
export default class Instagram extends React.Component {
// ... previous code here
render() {
return (
<div className="post-wrapper">
{/* map through our posts here */}
{this.state.photos &&
this.state.photos.map(
({ src, url, id, likes, comments, caption }) => (
<a
href={url}
target="_blank"
className="post-item"
rel="noopener noreferrer"
key={id}
>
<img
src={src}
className="post-image"
alt={caption.substring(0, 40)}
/>
</a>
)
)}
</div>
)
}
}
So we are saying that, if photos is not empty or in that sense equals true, run the items in the array and show me the images.
But to get a more Instagram-like experience, we also want to show the number of likes and comments on hover. Now for this, we need to add some CSS as well, so please note the import at the top. I won't go into details about the CSS here, but you can find the code on my Github here.
import React from "react"
// Styles
import "./instagram.css"
export default class Instagram extends React.Component {
// ... previous code here
render() {
return (
<div className="post-wrapper">
{/* map through our posts here */}
{this.state.photos &&
this.state.photos.map(
({ src, url, id, likes, comments, caption }) => (
<a
href={url}
target="_blank"
className="post-item"
rel="noopener noreferrer"
key={id}
>
<img
src={src}
className="post-image"
alt={caption.substring(0, 40)}
/>
{/* */}
<div className="post-item-info">
<ul>
<li className="post-item-likes">
<span role="img" aria-label="heart">
<svg
width="1em"
height="1em"
viewBox="0 0 24 24"
fill="white"
style={{
fontSize: "14px",
lineHeight: "1.45",
}}
>
<path d="M12 4.435C10.011-.964 0-.162 0 8.003 0 12.071 3.06 17.484 12 23c8.94-5.516 12-10.929 12-14.997C24-.115 14-.996 12 4.435z"></path>
</svg>
</span>{" "}
{likes !== null ? likes.toLocaleString() : 0}
</li>
<li className="post-item-comments">
<span role="img" aria-label="speech-balloon">
<svg
width="1em"
height="1em"
viewBox="0 0 24 24"
fill="white"
style={{
fontSize: "14px",
lineHeight: "1.45",
}}
>
<path d="M24 9.874C24 4.42 18.627 0 12 0S0 4.42 0 9.874c0 4.512 3.678 8.317 8.701 9.496L12 24l3.299-4.63C20.322 18.19 24 14.385 24 9.874z"></path>
</svg>
</span>{" "}
{comments !== null ? comments.toLocaleString() : 0}
</li>
</ul>
</div>
</a>
)
)}
</div>
)
}
}
At last, we want to show something while we are fetching the posts so that the user knows something is going on in the background. That's why we have our loading boolean in our state.
You might have noticed already, but after we fetched our posts and stored them in our state, we are also setting our loading boolean to false. With this in mind, all that is left is to have an if-else statement in our JSX to toggle between loading and showing pictures.
// ... imports here
export default class Instagram extends React.Component {
state = { photos: [], loading: true }
// ... your specifications here
async componentDidMount() {
try {
// ... fetching posts here and then set loading state to false
this.setState({ photos, loading: false })
} catch (error) {
console.error(error)
}
}
render() {
return (
<div className="post-wrapper">
{this.state.loading === true ? (
<div style={{ textAlign: "center" }}>
<h1>Loading ...</h1>
</div>
) : (
this.state.photos &&
this.state.photos.map(
({ src, url, id, likes, comments, caption }) => (
// ... previous code here
)
)
)}
</div>
)
}
}
import React from "react"
// Styles
import "./instagram.css"
export default class Instagram extends React.Component {
state = { photos: [], loading: true }
// Your specifications
INSTAGRAM_ID = "787132"
THUMBNAIL_WIDTH = 640
PHOTO_COUNT = 30
async componentDidMount() {
try {
// Hack from https://stackoverflow.com/a/47243409/2217533
const response = await fetch(
`https://www.instagram.com/graphql/query?query_id=17888483320059182&variables={"id":"${this.INSTAGRAM_ID}","first":${this.PHOTO_COUNT},"after":null}`
)
const { data } = await response.json()
const photos = data.user.edge_owner_to_timeline_media.edges.map(
({ node }) => {
const { id } = node
const comments = node.edge_media_to_comment.count
const likes = node.edge_media_preview_like.count
const caption = node.edge_media_to_caption.edges[0].node.text
const thumbnail = node.thumbnail_resources.find(
thumbnail => thumbnail.config_width === this.THUMBNAIL_WIDTH
)
const { src, config_width: width, config_height: height } = thumbnail
const url = `https://www.instagram.com/p/${node.shortcode}`
return {
id,
caption,
src,
width,
height,
url,
comments,
likes,
}
}
)
this.setState({ photos, loading: false })
} catch (error) {
console.error(error)
}
}
render() {
return (
<div className="post-wrapper">
{this.state.loading === true ? (
<div style={{ textAlign: "center" }}>
<h1>Loading ...</h1>
</div>
) : (
this.state.photos &&
this.state.photos.map(
({ src, url, id, likes, comments, caption }) => (
<a
href={url}
target="_blank"
className="post-item"
rel="noopener noreferrer"
key={id}
>
<img
src={src}
className="post-image"
alt={caption.substring(0, 40)}
/>
{/* */}
<div className="post-item-info">
<ul>
<li className="post-item-likes">
<span role="img" aria-label="heart">
<svg
width="1em"
height="1em"
viewBox="0 0 24 24"
fill="white"
style={{
fontSize: "14px",
lineHeight: "1.45",
}}
>
<path d="M12 4.435C10.011-.964 0-.162 0 8.003 0 12.071 3.06 17.484 12 23c8.94-5.516 12-10.929 12-14.997C24-.115 14-.996 12 4.435z"></path>
</svg>
</span>{" "}
{likes !== null ? likes.toLocaleString() : 0}
</li>
<li className="post-item-comments">
<span role="img" aria-label="speech-balloon">
<svg
width="1em"
height="1em"
viewBox="0 0 24 24"
fill="white"
style={{
fontSize: "14px",
lineHeight: "1.45",
}}
>
<path d="M24 9.874C24 4.42 18.627 0 12 0S0 4.42 0 9.874c0 4.512 3.678 8.317 8.701 9.496L12 24l3.299-4.63C20.322 18.19 24 14.385 24 9.874z"></path>
</svg>
</span>{" "}
{comments !== null ? comments.toLocaleString() : 0}
</li>
</ul>
</div>
</a>
)
)
)}
</div>
)
}
}
And the CSS:
.post-wrapper {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.post-item {
position: relative;
height: 100%;
flex: 1 0 12rem;
margin: 0 1rem 2rem;
color: #fff;
}
.post-image {
border-radius: 0.4rem;
width: 100%;
display: block;
box-shadow: 0 15px 25px rgba(0, 0, 0, 0.2);
cursor: pointer;
}
.post-item-likes {
margin-right: 1.5rem;
}
.post-item:hover .post-item-info,
.post-item:focus .post-item-info {
display: grid;
place-items: center;
position: absolute;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.3);
}
.post-item-info {
display: none;
border-radius: 0.4rem;
}
.post-item-info ul {
padding: 0;
margin: 0;
}
.post-item-info li {
display: inline;
}
Thanks so much for reading this far and feel free to reach out to me anytime, on my website or Twitter π And if you like to read more, make sure to check out my other posts on my blog!