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

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

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.

Table Of Contents

Set up your React Component

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!

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

  2. The variables below are specifications for our fetch call later. By switching out the username at the end, you can use this URL:

instagram.com/web/search/topsearch/context=..

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:

  • 150
  • 240
  • 320
  • 480
  • 640

Fetch your Instagram posts with Async/Await

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.

Display your posts and show likes on hover

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>
    )
  }
}

Implement a loading state while fetching posts

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>
    )
  }
}

The final result

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;
}

That’s pretty much it!

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!