Using Variables in a StaticQuery
December 13, 2018 at 10:05pmUsing Variables in a StaticQuery
December 13, 2018 at 10:05pmHey Folks - I'm using
gatsby-image
in my latest project and I'm finding it a bit cumbersome to have to write a whole query just to display an image. I would love to just have a component that I pass a src to and it does it all under the hood.So - I'm writing a component with StaticQuery that looks like this:
import React from 'react';import Img from 'gatsby-image';import { StaticQuery, graphql } from 'gatsby';function renderImage({ file }) {return <Img fluid={file.childImageSharp.fluid} />}const MyImg = function (props) {return <StaticQueryquery={graphql`query {file(relativePath: { eq: "wes-and-scott.jpg" }) {childImageSharp {fluid(maxWidth: 1000) {...GatsbyImageSharpFluid}}}}`}render={renderImage}/>}export default MyImg;
Now that works amazingly well, BUT I can't make the filename dynamic.
I read in the docs that Static Query can't take variables - only pages. But I don't want my images to be associated with a page - I want to use this component anywhere I want - like a regular img tag.
I tried string interpolation and that doesn't work.
Any way to get around this? Am I thinking about this in the wrong way?
December 13, 2018 at 10:16pm
Hey Wes — definitely agree that allowing components w/ StaticQuerys take arguments would be amazing. This isn't something we've yet tackled but it should be doable with sufficient babel/code analysis magic :-)
One constraint would be that variables passed to these components would still need to be "static" meaning they're numbers/strings.
I have not used gatsby, but I think you can hack the graphql. This is something untested though:
import React from 'react';import Img from 'gatsby-image';import { StaticQuery, graphql } from 'gatsby';function renderImage({ file }) {return <Img fluid={file.childImageSharp.fluid} />}const MyImg = function (props) {function hackTheQuery(src) {return `query {file(relativePath: { eq: "${src}" }) {childImageSharp {fluid(maxWidth: 1000) {...GatsbyImageSharpFluid}}}}`}return <StaticQueryquery={graphql([hackTheQuery(props.src)])}render={renderImage}/>}export default MyImg;
and this is what I use in order to reuse my Static Queries and hack them into being somewhat dynamic:
import React from 'react';import PropTypes from 'prop-types';import { graphql, StaticQuery } from 'gatsby';function TestimonialQuery({ children, identifier }) {return (<StaticQueryquery={graphql`query {allContentfulTestimonial {edges {node {identifierimage {titlefluid(maxWidth: 437) {...GatsbyContentfulFluid_withWebp}}}}}}`}render={({ allContentfulTestimonial }) =>children(allContentfulTestimonial.edges.find(({ node }) => node.identifier === identifier,),)}/>);}TestimonialQuery.propTypes = {children: PropTypes.func.isRequired,identifier: PropTypes.string.isRequired,};export default React.memo(TestimonialQuery);
Then I just use it as a render prop:
<TestimonialQuery>{node => <div>node.identifier</div>}</TestimonialQuery>
The same could be done by passing file names to your query . It's not the most elegant, but it seems to work for well for me and reduces boilerplate.
I had looked into this at one point, this issue discussed some attempts at working some magic with Babel but never seemed to reach a conclusion: https://github.com/gatsbyjs/gatsby/issues/2293#issuecomment-418542566
Of existing Gatsby starters and showcase sites where lots of
<Img>
components are used, the common pattern seems to be with the image data queried in a big list of edges like in this starter (https://github.com/greglobinski/gatsby-starter-hero-blog/blob/3db65f5badd52c5b50924180c0b0f7d68870bb07/src/components/Blog/Item.js#L32) or this one (https://github.com/LekoArts/gatsby-starter-portfolio-emma/blob/1e91b715523b5506f3c29308d8fb7bf0d38ce4dc/src/components/ProjectListing.jsx#L84).I guess that pattern doesn't match your use case with
StaticQuery
exactly though. Be careful with that approach, it will download the metadata (including base64 encoded image!) of EVERY SINGLE image you have and then filter it on the client. Depending on how many images you have, that can chew up a lot of bandwidth.
A few people on twitter were recommending this approach:
Say I have ~200 images. Would this be a memory issue? I would assume it would be but so many people have said they go this way
I've settled on this for now - unsure if it will cause perf issues down the road - we will see! Thanks for the help everyone!
import React from 'react';import Img from 'gatsby-image';import { StaticQuery, graphql } from 'gatsby';function renderImage(file) {console.log(file);return <Img fluid={file.node.childImageSharp.fluid} />}const MyImg = function (props) {return <StaticQueryquery={graphql`query {images: allFile(filter:{ extension: { regex: "/jpeg|jpg|png|gif/"}}) {edges {node {extensionrelativePathchildImageSharp {fluid(maxWidth: 1000) {...GatsbyImageSharpFluid}}}}}}`}render={({ images }) => renderImage(images.edges.find(image => image.node.relativePath === props.src))}/>}export default MyImg;
December 14, 2018 at 1:51pm
I love Gatsby, and all I want for Christmas is for this to be a reality: https://github.com/gatsbyjs/gatsby/issues/9843
Hey Wes, what I've done, without seeing too much of a performance hit, seems to be exactly what you're suggesting: namely, create a component that queries all image nodes in a specific location and filter the results, finding match and returning it (or a default placeholder, if no match is found).
In order to speed things up, I've broken the images into groups (maybe I have Bio images, Cover images, and Dog images... I'd have <DogImage /> <BioImage /> and <CoverImage />).
And they point to
images/dogs
, images/bios
, images/covers
I'd like to look into the performance implications of this, too. I'm wondering if, since to my understanding the first render and the StaticQuery itself happens on build, and not browser-side, the hit would even be noticeable.
just as a follow up, I set up the following netlify to test this: https://dazzling-bohr-1d9d3d.netlify.com/ using 200 random unsplash images.
The homepage uses 200 components, each with its own StaticQuery and /page-2/ uses a single component with a single StaticQuery, being filtered.
The github is here: https://github.com/cborchert/GatsbyImageTest
Not sure how much to trust the Chrome profiler, but here's the first page's snapshot
And the second
first off, thanks for putting in that work. But to be honest, it kind of misses my point - which is reusing the same image component for multiple pages. Where the individual
StaticQuery
s would only load exactly the image data that is required for that page, while the reusable component in question would load all images even if they're not used on that page.