Gatsby: How to use graphql to display array of images from json ?

Created on 21 Sep 2019  路  12Comments  路  Source: gatsbyjs/gatsby

{
  menu: allMenuJson {
    nodes {
      id
      title
      description
      link
      image {
        src {
          childImageSharp {
            fluid {
              ...GatsbyImageSharpFluid
            }
          }
        }
      }
    }
  }
  } 

menu.json contain the following

[
{
"image": {
            "src": "contact.png"
        },
},
{
"image": {
            "src": "service.png"
        },
}

]

images are located at
src/images/menu/contact.png
src/images/menu/service.png

How do i query to display the image using gatsby-image component?
<Img fluid={menu.image.src..childImageSharp.fluid} /> is not working

awaiting author response question or discussion

Most helpful comment

@maheshkumar2150 i have a possible solution for your issue.

Here are the steps i took to triage your issue.

  • Created a new gatsby site based on the default starter as it would be more convenient for this case, as it has already the gatsby-image, and it's accompanying plugins gatsby-transformer-sharp and gatsby-plugin-sharp so that we can take advantage of the full benefits of the plugins.
  • Also added gatsby-transformer-json to the site.
  • Changed the gatsby-config.js to the following, leaving in only the relevant portion of the content :
module.exports = {
  .....
  plugins: [
    ....
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/images`,
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `./src/data/`,
      },
    },
    `gatsby-transformer-sharp`,
    `gatsby-plugin-sharp`,
    `gatsby-transformer-json`,
    ....
    }
  ],
}

The folder /src/data will contain 2 json files, one called menu.json and another called other.json.
The /src/images folder is the default one that is used in gatsby.

  • Added the following content to menu.json.
[
    {
        "title":"Contact",
        "description":"This is the contact page",
        "link":"/contact/",
        "image":{
            "src":"contact.png"
        }
    },
    {
        "title":"Services",
        "description":"This is the services page",
        "link":"/services/",
        "image":{
            "src":"service.jpg"
        }
    }
]
  • Added the following content to other.json:
[
    {
        "content":"Doggo ipsum long doggo smol borking doggo with a long snoot for pats long woofer very taste wow I am bekom fat doggorino, pats h*ck borking doggo wow such tempt floofs, pats wow very biscit most angery pupper I have ever seen long water shoob. Heckin good boys fluffer boofers adorable doggo woofer, puggorino pats. Shibe big ol bork blop, what a nice floof pupperino. Heckin good boys long bois bork waggy wags long bois, long woofer length boy much ruin diet.",
        "image":{
            "src":"randomimage_1.jpg"
        }
    },{
        "content":"Aqua doggo big ol borkf floofs long water shoob blep heckin good boys and girls, porgo very good spot you are doing me the shock adorable doggo.  Shibe aqua doggo vvv extremely cuuuuuute shoob doge, such treat borkf yapper. Smol heckin good boys and girls puggorino tungg pats you are doing me a frighten much ruin diet, borkf fluffer floofs very taste wow.",
        "image":{
            "src":"randomimage_2.jpg"
        }
    }
]

The content added is a extrapolation based on your description.

  • Grabbed 4 random images to match the contents of the json files.

Now 2 approaches to your issue will be documented. The first is more simple and straightforward the other is a bit more advanced, as it will use some gatsby apis.

Going to start with the simple approach.

  • Modified /src/pages/index.js to the following:
import React from "react"
import { Link, graphql } from "gatsby"

import Layout from "../components/layout"
import Img from "gatsby-image"
import SEO from "../components/seo"

const IndexPage = ({ data }) => {
  const { menu, allImageContent, other } = data
  return (
    <Layout>
      <SEO title="Home" />
      <h1>Simple example</h1>
      <div>
        <div style={{ display: "flex", justifyContent: "space-between" }}>
          <div style={{ margin: "1.3rem" }}>
            <h3>menu</h3>
            <div>
              {menu.edges.map(item => {

                // fetches the image based on the value that that is set on the appropriate json element
                const imageForMenu = allImageContent.edges.find(
                  element =>
                    element.node.fluid.originalName === item.node.image.src
                )

                return (
                  <div key={item.node.id}>
                    <h4>{item.node.title}</h4>
                    <h5>{item.node.description}</h5>
                    <Img fluid={imageForMenu.node.fluid} />
                    <Link to={item.node.link}>{item.node.title}</Link>
                  </div>
                )
              })}
            </div>
          </div>
          <div>
            <div style={{ margin: "1.3rem" }}>
              <h3>content</h3>
              {other.edges.map(item => {
                // fetches the image based on the value that that is set on the appropriate json element
                const imageforOther = allImageContent.edges.find(
                  x => x.node.fluid.originalName === item.node.image.src
                )
                return (
                  <div key={item.node.id}>
                    <Img fluid={imageforOther.node.fluid}/>
                    <h4 style={{margin:'0.8rem'}}>
                      {item.node.content}
                    </h4>

                  </div>
                )
              })}
            </div>
          </div>
        </div>
      </div>
      <Link to="/page-2/">Go to page 2</Link>
    </Layout>
  )
}
/**
 * query that will fetch all of the json file content and also all of the images
 */
export const pagequery = graphql`
  query {
    menu: allMenuJson {
      edges {
        node {
          id
          title
          description
          link
          image {
            src
          }
        }
      }
    }
    other: allOtherJson {
      edges {
        node {
          id
          content
          image {
            src
          }
        }
      }
    }
    allImageContent: allImageSharp {
      edges {
        node {
          fluid(maxWidth: 300) {
            originalName
            ...GatsbyImageSharpFluid
          }
        }
      }
    }
  }
`

export default IndexPage

This a is pretty straightforward page that contains a page query, that will fetch all of the contents of the json files and also the images, goes over each element for each file, grabs the appropriate image based on what is defined on the json and displays it.

  • Issued gatsby develop and opened up http://localhost:8000 and i'm presented with the following:
    maheshkumar-1

On the left is the contents of the menu.json and the corresponding images and on the right the contents of the other.json and the corresponding images.

Moving onto the more advance approach.

Gatsby among a lot of diferent t features available, lets you manipulate the nodes that are created during the build process, With this you can grab a node from one source and extend it to incorporate another. And that's what is going to be described below:

  • Started by modifying gatsby-node.js with the following code:
/**
 * gatsby internal api hook that will be triggered after each node is created
 * @param {Object} node is the internal gatsby node "object" that will be available in the data layer i.e graphQL layer
 * @param {Object} actions contains the set of options available.
 */
exports.onCreateNode = ({ node, actions }) => {
  // destructures the needed action
  const { createNodeField } = actions
  //
  // checks the gatsby node type
  // in this case we are looking for the json content that exists and transformed by the gatsby-transformer-json plugin
  if (node.internal.type === "MenuJson") {
    // extends the existing gatsby node with a new field, later accessible via the fields graphql node.
    createNodeField({
      node, // the current node
      name: `menuImage`, // defines a name for the new element being added.
      value: `../images/${node.image.src}`, //Injects the value, this will be relative to the path of the json, it will look into /src/images
    })
  }
  if (node.internal.type === "OtherJson") {
    // extends the existing gatsby node with a new field, later accessible via the fields graphql node.
    createNodeField({
      node, // the current node
      name: `otherImage`, // defines a name for the new element being added.
      value: `../images/${node.image.src}`, //Injects the value, this will be relative to the path of the json, it will look into /src/images
    })
  }
}

What is happening in this snippet of code, is the following, gatsby will check the node type is the one associated to the one created by gatsby-transformer-json and will extend it and inject a new field to that node that will contain a reference to the image file to be used. For more information about this see here also here and here.

  • Modified /src/pages/page-2.js to the following content:
import React from "react"
import { Link, graphql } from "gatsby"
import Img from "gatsby-image"
import Layout from "../components/layout"
import SEO from "../components/seo"

const SecondPage = ({ data }) => {
  const { menu, other } = data
  return (
    <Layout>
      <SEO title="Page two" />
      <h1>Page with nodes extended and combined</h1>
      <div style={{ display: "flex", justifyContent: "space-between" }}>
        <div style={{ margin: "1.3rem" }}>
          <h3>menu</h3>
          <div>
            {menu.edges.map(item => {
              return (
                <div key={item.node.id}>
                  <h4>{item.node.title}</h4>
                  <h5>{item.node.description}</h5>
                  <Img
                    fluid={item.node.fields.menuImage.childImageSharp.fluid}
                  />
                  <Link to={item.node.link}>{item.node.title}</Link>
                </div>
              )
            })}
          </div>
        </div>
        <div style={{ margin: "1.3rem" }}>
          <h3>content</h3>
          {
            other.edges.map(item=>{
              return (
                <div key={item.node.id}>
                  <Img fluid={item.node.fields.otherImage.childImageSharp.fluid}/>
                  <h4 style={{margin:'0.8rem'}}>
                      {item.node.content}
                    </h4>
                  </div>
              )
            })
          }
        </div>
      </div>
      <Link to="/">Go back to the homepage</Link>
    </Layout>
  )
}
// page query with the updated fields that were created in gatsby-node.js making the query more streamlined.
export const query = graphql`
  query {
    menu: allMenuJson {
      edges {
        node {
          id
          title
          description
          link
          image {
            src
          }
          fields {
            menuImage {
              childImageSharp {
                fluid(maxWidth: 300) {
                  ...GatsbyImageSharpFluid
                }
              }
            }
          }
        }
      }
    }
    other: allOtherJson {
      edges {
        node {
          id
          content
          fields {
            otherImage {
              childImageSharp {
                fluid(maxWidth: 300) {
                  ...GatsbyImageSharpFluid
                }
              }
            }
          }
        }
      }
    }
  }
`
export default SecondPage
  • Stopped the development server and restarted it and issued gatsby clean && gatsby develop to purge any cached data and restart the development server once again. Opened up a new browser window to http://localhost:8000/page-2/ and i'm presented with the following:

maheshkumar-2

Once again the content is similar as it was on the index page. But now you avoid filtering the image data associated to each element through javascript and have it already directly available. With this it will make the queries more streamlined.

Adjusting to your case will probably won't be to difficult to implement. I've setup a repo with the code used for this issue here so that you can look it at your own pace.

Depending on your knowledge of gatsby i would start with the first approach, fiddle with it and when you're more knowledgeable of the apis and internals of gatsby i would move into the second one and go from there.

Feel free to provide feedback so that we can close this issue or continue to work on it until we find a suitable solution.

All 12 comments

<Img fluid={menu.image.src..childImageSharp.fluid} /> is not working

Do you have those two dots between src and childImageSharp in your code? Because that will break it.

No there are no two dots. It is a mistake,
if i make the following changes in my menu.json it works.

[
{
"image": {
"src": "../images/menu/contact.png"
},
},
{
"image": {
"src": "./images/menu/service.png"
},
}
]

But i just want to keep only the image name in the json file. Is that possible?

@maheshkumar2150 short answer, the first approach will only work with gatsby-image and it's accompanying plugins gatsby-transformer-sharp and gatsby-plugin-sharp and also gatsby-source-filesystem if the images are in the same folder as the json file. Namely something like:

|root
  | content
    -menu.json
    -contact.png
   - service.png

Otherwise you have to include the relative path to the images as technically gatsby would not know where to look.

Let me see if i can create a small repo that could offer a workaround for your issue.

Do you mind waiting a bit?

Yes. I can wait.

Your solution is good.

I have data folder. In that folder, i have few json files.
The problem is i have to include all the images in that data folder. That is the only drawback.

data/menu.json
data/other.json

if gatsby can find the images in data/menu/ and data/other/ it will easier to manage images properly

@maheshkumar2150 i have a possible solution for your issue.

Here are the steps i took to triage your issue.

  • Created a new gatsby site based on the default starter as it would be more convenient for this case, as it has already the gatsby-image, and it's accompanying plugins gatsby-transformer-sharp and gatsby-plugin-sharp so that we can take advantage of the full benefits of the plugins.
  • Also added gatsby-transformer-json to the site.
  • Changed the gatsby-config.js to the following, leaving in only the relevant portion of the content :
module.exports = {
  .....
  plugins: [
    ....
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/images`,
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `./src/data/`,
      },
    },
    `gatsby-transformer-sharp`,
    `gatsby-plugin-sharp`,
    `gatsby-transformer-json`,
    ....
    }
  ],
}

The folder /src/data will contain 2 json files, one called menu.json and another called other.json.
The /src/images folder is the default one that is used in gatsby.

  • Added the following content to menu.json.
[
    {
        "title":"Contact",
        "description":"This is the contact page",
        "link":"/contact/",
        "image":{
            "src":"contact.png"
        }
    },
    {
        "title":"Services",
        "description":"This is the services page",
        "link":"/services/",
        "image":{
            "src":"service.jpg"
        }
    }
]
  • Added the following content to other.json:
[
    {
        "content":"Doggo ipsum long doggo smol borking doggo with a long snoot for pats long woofer very taste wow I am bekom fat doggorino, pats h*ck borking doggo wow such tempt floofs, pats wow very biscit most angery pupper I have ever seen long water shoob. Heckin good boys fluffer boofers adorable doggo woofer, puggorino pats. Shibe big ol bork blop, what a nice floof pupperino. Heckin good boys long bois bork waggy wags long bois, long woofer length boy much ruin diet.",
        "image":{
            "src":"randomimage_1.jpg"
        }
    },{
        "content":"Aqua doggo big ol borkf floofs long water shoob blep heckin good boys and girls, porgo very good spot you are doing me the shock adorable doggo.  Shibe aqua doggo vvv extremely cuuuuuute shoob doge, such treat borkf yapper. Smol heckin good boys and girls puggorino tungg pats you are doing me a frighten much ruin diet, borkf fluffer floofs very taste wow.",
        "image":{
            "src":"randomimage_2.jpg"
        }
    }
]

The content added is a extrapolation based on your description.

  • Grabbed 4 random images to match the contents of the json files.

Now 2 approaches to your issue will be documented. The first is more simple and straightforward the other is a bit more advanced, as it will use some gatsby apis.

Going to start with the simple approach.

  • Modified /src/pages/index.js to the following:
import React from "react"
import { Link, graphql } from "gatsby"

import Layout from "../components/layout"
import Img from "gatsby-image"
import SEO from "../components/seo"

const IndexPage = ({ data }) => {
  const { menu, allImageContent, other } = data
  return (
    <Layout>
      <SEO title="Home" />
      <h1>Simple example</h1>
      <div>
        <div style={{ display: "flex", justifyContent: "space-between" }}>
          <div style={{ margin: "1.3rem" }}>
            <h3>menu</h3>
            <div>
              {menu.edges.map(item => {

                // fetches the image based on the value that that is set on the appropriate json element
                const imageForMenu = allImageContent.edges.find(
                  element =>
                    element.node.fluid.originalName === item.node.image.src
                )

                return (
                  <div key={item.node.id}>
                    <h4>{item.node.title}</h4>
                    <h5>{item.node.description}</h5>
                    <Img fluid={imageForMenu.node.fluid} />
                    <Link to={item.node.link}>{item.node.title}</Link>
                  </div>
                )
              })}
            </div>
          </div>
          <div>
            <div style={{ margin: "1.3rem" }}>
              <h3>content</h3>
              {other.edges.map(item => {
                // fetches the image based on the value that that is set on the appropriate json element
                const imageforOther = allImageContent.edges.find(
                  x => x.node.fluid.originalName === item.node.image.src
                )
                return (
                  <div key={item.node.id}>
                    <Img fluid={imageforOther.node.fluid}/>
                    <h4 style={{margin:'0.8rem'}}>
                      {item.node.content}
                    </h4>

                  </div>
                )
              })}
            </div>
          </div>
        </div>
      </div>
      <Link to="/page-2/">Go to page 2</Link>
    </Layout>
  )
}
/**
 * query that will fetch all of the json file content and also all of the images
 */
export const pagequery = graphql`
  query {
    menu: allMenuJson {
      edges {
        node {
          id
          title
          description
          link
          image {
            src
          }
        }
      }
    }
    other: allOtherJson {
      edges {
        node {
          id
          content
          image {
            src
          }
        }
      }
    }
    allImageContent: allImageSharp {
      edges {
        node {
          fluid(maxWidth: 300) {
            originalName
            ...GatsbyImageSharpFluid
          }
        }
      }
    }
  }
`

export default IndexPage

This a is pretty straightforward page that contains a page query, that will fetch all of the contents of the json files and also the images, goes over each element for each file, grabs the appropriate image based on what is defined on the json and displays it.

  • Issued gatsby develop and opened up http://localhost:8000 and i'm presented with the following:
    maheshkumar-1

On the left is the contents of the menu.json and the corresponding images and on the right the contents of the other.json and the corresponding images.

Moving onto the more advance approach.

Gatsby among a lot of diferent t features available, lets you manipulate the nodes that are created during the build process, With this you can grab a node from one source and extend it to incorporate another. And that's what is going to be described below:

  • Started by modifying gatsby-node.js with the following code:
/**
 * gatsby internal api hook that will be triggered after each node is created
 * @param {Object} node is the internal gatsby node "object" that will be available in the data layer i.e graphQL layer
 * @param {Object} actions contains the set of options available.
 */
exports.onCreateNode = ({ node, actions }) => {
  // destructures the needed action
  const { createNodeField } = actions
  //
  // checks the gatsby node type
  // in this case we are looking for the json content that exists and transformed by the gatsby-transformer-json plugin
  if (node.internal.type === "MenuJson") {
    // extends the existing gatsby node with a new field, later accessible via the fields graphql node.
    createNodeField({
      node, // the current node
      name: `menuImage`, // defines a name for the new element being added.
      value: `../images/${node.image.src}`, //Injects the value, this will be relative to the path of the json, it will look into /src/images
    })
  }
  if (node.internal.type === "OtherJson") {
    // extends the existing gatsby node with a new field, later accessible via the fields graphql node.
    createNodeField({
      node, // the current node
      name: `otherImage`, // defines a name for the new element being added.
      value: `../images/${node.image.src}`, //Injects the value, this will be relative to the path of the json, it will look into /src/images
    })
  }
}

What is happening in this snippet of code, is the following, gatsby will check the node type is the one associated to the one created by gatsby-transformer-json and will extend it and inject a new field to that node that will contain a reference to the image file to be used. For more information about this see here also here and here.

  • Modified /src/pages/page-2.js to the following content:
import React from "react"
import { Link, graphql } from "gatsby"
import Img from "gatsby-image"
import Layout from "../components/layout"
import SEO from "../components/seo"

const SecondPage = ({ data }) => {
  const { menu, other } = data
  return (
    <Layout>
      <SEO title="Page two" />
      <h1>Page with nodes extended and combined</h1>
      <div style={{ display: "flex", justifyContent: "space-between" }}>
        <div style={{ margin: "1.3rem" }}>
          <h3>menu</h3>
          <div>
            {menu.edges.map(item => {
              return (
                <div key={item.node.id}>
                  <h4>{item.node.title}</h4>
                  <h5>{item.node.description}</h5>
                  <Img
                    fluid={item.node.fields.menuImage.childImageSharp.fluid}
                  />
                  <Link to={item.node.link}>{item.node.title}</Link>
                </div>
              )
            })}
          </div>
        </div>
        <div style={{ margin: "1.3rem" }}>
          <h3>content</h3>
          {
            other.edges.map(item=>{
              return (
                <div key={item.node.id}>
                  <Img fluid={item.node.fields.otherImage.childImageSharp.fluid}/>
                  <h4 style={{margin:'0.8rem'}}>
                      {item.node.content}
                    </h4>
                  </div>
              )
            })
          }
        </div>
      </div>
      <Link to="/">Go back to the homepage</Link>
    </Layout>
  )
}
// page query with the updated fields that were created in gatsby-node.js making the query more streamlined.
export const query = graphql`
  query {
    menu: allMenuJson {
      edges {
        node {
          id
          title
          description
          link
          image {
            src
          }
          fields {
            menuImage {
              childImageSharp {
                fluid(maxWidth: 300) {
                  ...GatsbyImageSharpFluid
                }
              }
            }
          }
        }
      }
    }
    other: allOtherJson {
      edges {
        node {
          id
          content
          fields {
            otherImage {
              childImageSharp {
                fluid(maxWidth: 300) {
                  ...GatsbyImageSharpFluid
                }
              }
            }
          }
        }
      }
    }
  }
`
export default SecondPage
  • Stopped the development server and restarted it and issued gatsby clean && gatsby develop to purge any cached data and restart the development server once again. Opened up a new browser window to http://localhost:8000/page-2/ and i'm presented with the following:

maheshkumar-2

Once again the content is similar as it was on the index page. But now you avoid filtering the image data associated to each element through javascript and have it already directly available. With this it will make the queries more streamlined.

Adjusting to your case will probably won't be to difficult to implement. I've setup a repo with the code used for this issue here so that you can look it at your own pace.

Depending on your knowledge of gatsby i would start with the first approach, fiddle with it and when you're more knowledgeable of the apis and internals of gatsby i would move into the second one and go from there.

Feel free to provide feedback so that we can close this issue or continue to work on it until we find a suitable solution.

Man, you are an extraordinary person.
Thank you so much!

@maheshkumar2150 no need to thank, glad that i was able to help out and offer some insights on your issue.

@maheshkumar2150 can we close this issue?

I'm going to close this issue, feel free to open it back up or open a new issue if you run into something new @maheshkumar2150 馃檪

Thanks for the help on it @jonniebigodes!!

@gillkyle i left it open, so that i could "hear" some feedback from @maheshkumar2150 to confirm that he had it up and running and was able to solve is issue. No need to thank, glad that i was able to supply a clear and helpful resolution for it

@jonniebigodes thanks for your amazing solution, the "advance approach" works really well.
One appendix, if you happen to have a list of images, this works for me:

in data.json:

{
  // other properties
 "images": [
    {
      "src": "test.png"
    },
    {
     "src": "test1.png"
    }
 ]
}

in gatsy-node.js

  if (node.internal.type === "DataJson") {
    createNodeField({
      node, 
      name: `image`,
      value: node.images.map(image => `./../src/images/${image.src}`)
    })
  }

@jonniebigodes 1 year later, your answer came to my rescue, thank you!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ferMartz picture ferMartz  路  3Comments

ghost picture ghost  路  3Comments

3CordGuy picture 3CordGuy  路  3Comments

kalinchernev picture kalinchernev  路  3Comments

andykais picture andykais  路  3Comments