Gatsby - Using Dynamic Images Inside a Component

947 Views Asked by At

I have a component that works as a collapsible ui element where an image is loaded when opened. These collapsible elements are part of a list, and I need the image to be a variable since it's different on each list item.

I understand that I need to use Dynamic Images in Gatsby, since Static Images can't use props, and I need to resolve the image name from a prop.

Trying to use Dynamic Image, I'm getting "undefined" when trying to resolve the image from a prop. According to this post, you can't use GraphQL inside a component that is not in the /pages directory. Instead they are suggesting the use of Fragment, and that's where I'm stuck trying to figure out how to use it in my specific case.

Here's what I'm trying to do in src/components/Collapsible.js:

import React, { useState } from "react";
import { GatsbyImage } from "gatsby-plugin-image";

function Collapsible({ hit, children }) {
  const [isOpen, setIsOpen] = useState(false);

  const image = "../images/reference/render/" + hit.name;

  return !isOpen ? (
    <tr className="row" onClick={() => setIsOpen(!isOpen)}>
      {children}
    </tr>
  ) : (
    <tr>
      <td colSpan="4">
        <div className="material-card">
          <div>
            <h1 onClick={() => setIsOpen(!isOpen)}>{hit.name}</h1>
            <p>{hit.category}</p>
            <div className="thumbnail">
              <GatsbyImage image={image}/>
            </div>
          </div>
        </div>
      </td>
    </tr>
  );
}

export default Collapsible;

Somehow I'm supposed to be able to create a Fragment using my GraphQl query, which looks like this:

query Image {
  file(relativePath: {eq: "reference/render/image1.png"}) {
    id
    childImageSharp {
      gatsbyImageData(
        width: 300
        placeholder: NONE
        quality: 75
        layout: CONSTRAINED
      )
    }
  }
}

Any ideas?

1

There are 1 best solutions below

2
On

This line:

const image = "../images/reference/render/" + hit.name;

Is returning a path, while when using GatsbyImage you need to provide a full object with gatsbyImageData as an image property. The approach will work when rendering "standard" <img> in the src attribute but not with GatsbyImage since your image variable doesn't contain the data that GatsbyImage needs.

Moreover, your GraphQL query fragment I'm not sure where it's used, but not in any snippet you've shared. The problem here is that you are trying to combine dynamic images with static queries, where you can't use dynamic filters: hit.name will be something dynamic and you cannot combine it with static queries hence they don't accept dynamic parameters.

That said, I think a valid workaround will be using a useStaticQuery hook to get allFile node with gatsbyImageData in it and filter the array using hit.name or with the relativePath. allFile will hold all your images so that you only need to filter it using JavaScript.

import React from "react";
import { useStaticQuery, graphql } from "gatsby";

function Collapsible({ hit, children }) {
  const data = useStaticQuery(graphql`
    query allImages {
      allFile {
        edges {
          node {
            childImageSharp {
              gatsbyImageData(
                width: 300
                placeholder: NONE
                quality: 75
                layout: CONSTRAINED
              )
            }
          }
        }
      }
    }
  `);

   const image = data.allFile.edges.node.filter(node => node.relativePath === "../images/reference/render/" + hit.name) 

  return !isOpen ? (
    <tr className="row" onClick={() => setIsOpen(!isOpen)}>
      {children}
    </tr>
  ) : (
    <tr>
      <td colSpan="4">
        <div className="material-card">
          <div>
            <h1 onClick={() => setIsOpen(!isOpen)}>{hit.name}</h1>
            <p>{hit.category}</p>
            <div className="thumbnail">
              <GatsbyImage image={image} />
            </div>
          </div>
        </div>
      </td>
    </tr>
  );
}

Note: Test the query in the localhost:8000/___graphql before to ensure that the query is returning the correct data. Tweak the node => node.relativePath === "../images/reference/render/" + hit.name to match your specific filter, this is an example.

The static query will hold your data inside data.allFile, there, using hit.name you can filter the array of images and get the needed one. This image, since it's queried and holding gatsbyImageData, will be a valid image to use in GatsbyImage