[feat]: add ability to pass variables to StaticQuery #10482
Replies: 57 comments 8 replies
-
This mean that we cannot pass user input values as arguments for these types of components, right? I feel like it's going to be super confusing for users to be able to use variables but only static values are supported. Is there any way we can emphasize that "although you can pass a variable, it needs to be predefined"? I'm thinking something along the line of a Gatsby plugin that accept an array of strings. When passing a variable, it needs to be an element in the array. |
Beta Was this translation helpful? Give feedback.
-
This is a similar approach we take to generating wrappers in gatsby-mdx and I think it could be a fantastic approach not just for MDX content, but also potentially as a primitive for theming when the theme's image handling can be shadowed by the user without needing to write additional image queries themselves. Imagine a theme providing using an Image component that renders the result using a file in As @alexluong mentions, this could be confusing if you're expecting dynamic values. We could also do something similar to webpack's require.context and if you pass in a glob, we find all of the images that match, get them processed, and you can choose between them at runtime. At which point it's sort of "your responsibility" as the user to make sure you only access images that exist (or fall back if they don't. require.context is reasonable inspiration for providing a json object of potential image values to choose from). In any case, that's "part two" or "sometime later" functionality IMO and we should focus on enabling this for static values to start. |
Beta Was this translation helpful? Give feedback.
-
I like this, with one suggestion: Let users explicitly pass the variables as an object like so: <StaticQuery query={graphql`
query HeadingQuery($src: String!) {
File(relativePath: { eq: $src }) {
childImageSharp { ...etc }
}
`
}
variables={{ src }}
render={data => (
<Img fixed={data.path.to.query} />
)}
/> That way you could remap variable names or use nested objects. I first thought that the validation could be made as an eslint plugin, but that wouldn't work as we also need to check that the consumer of the component doesn't change the variable. I believe that would only be possible with something like TypeScript. Another approach (that would also satisfy my first suggestion) is to require a certain name for the query variable prop, eg: export default ({ queryVariables: { src } }) => {
<StaticQuery query={graphql`
query HeadingQuery($src: String!) {
File(relativePath: { eq: $src }) {
childImageSharp { ...etc } }
`}
render={data => (
<Img fixed={data.path.to.query} />
)}
/>
} That would be easily checkable for an eslint plugin (simply require that everytime a |
Beta Was this translation helpful? Give feedback.
-
Hello! I hope this gives you some feedback in terms of prioritising this feature in your internal development. |
Beta Was this translation helpful? Give feedback.
-
I ran into this trying to create a very similar wrapper as the one @KyleAMathews uses in the demo, and would be very happy with this as well. |
Beta Was this translation helpful? Give feedback.
-
My use case is when I use gatsby-background-image and I need to load different file for mobile vs desktop. It would be nice to be able to pass in a file name to the static query, as @KyleAMathews mentioned above. |
Beta Was this translation helpful? Give feedback.
-
Hiya! This issue has gone quiet. Spooky quiet. 👻 We get a lot of issues, so we currently close issues after 30 days of inactivity. It’s been at least 20 days since the last update here. If we missed this issue or if you want to keep it open, please reply here. You can also add the label "not stale" to keep this issue open! Thanks for being a part of the Gatsby community! 💪💜 |
Beta Was this translation helpful? Give feedback.
-
Plus one here im trying to optimize image loading bu at the same time dont want to write graphql queries for each image. |
Beta Was this translation helpful? Give feedback.
-
I have this use-case as well, for dynamically passing in an image name to the component. I want to use StaticQuery for the obvious benefit of the image sharp stuff, src for different screens etc. It seems to me that in a certain sense, the variables are already immutable, since those variables would start life as a prop on the component reference somewhere. So Gatsby needs to be able to quickly connect the StaticQuery variables to the component props, and then find each reference where the props are set. This definitely becomes difficult if the props are converted to other variable names before used within the query. So the first idea would be to force a direct correlation between the component props, and the variables being used in the query. This, at least, erases some chicken chasing with variable definitions and so on. I don't know how it could be done, given that a prop value might need some kind of mutation before use in the query, and mutation might necessitate using another variable name. Anyway, if you can lock in the correlation of query variables to props, you then have the issue of finding everywhere the component is referenced. Or specifically everywhere it is referenced with the given props in use, cause that's all that matters really. All I can suggest here is that Gatsby keep a kind of lookup table/cache that actually tracks all the components and where they are referenced. This data might be heavy to process, I don't know, but that data would also be useful for debugging, as it could then be used to generate an overview of your site with simple stats like "X component is used Y times across Z pages" and be able to list each page and component reference. That is handy data even if a little heavy to process! Finally, Gatsby would have no choice, because there simply is no other way to do it, to render additional copies of the StaticQuery with each alternate variable used. This is a given, I don't see any other way to get through this part, it simply has to run through the query multiple times with each different variable set used. So temp copies of the query would have to be generated with each variable replacement. All that said, I understand the normal way to do this would be query all the potential images from the page itself, and pass those into the component where used. The only reason I don't want to do this is that I'm trying to make the component more self-contained and not mess the page component with tons of queries for everything. If the component has everything it needs within itself, it's so much cleaner. Anyway I hope you get it solved some day! |
Beta Was this translation helpful? Give feedback.
-
UPDATE: Another day into this mess I got a way more elegant solution to share. First off, putting the createPages api into onPreExtractQueries is super fragile and does not really work. Do not try it. The goal:
The problems:
The solution: to problem 1. Create the image nodes with the createRemoteFileNode helper function, which I found out about here. Not sure if that is a proper use of it, but it works like a charm. Images are now available for querying.
IMPORTANT: If I do not await the creation of nodes, the bootstrapping process does not wait for the images to be downloaded, so nodes are not available in createPages query! In addition:
So, now I have the images in my graphQL schema, but in order to render an image in my dynamically created pages, I have to query for all images and filter out the one I need, similar to what was discussed in the Wes Bos twitter post. The solution works, but it's not really scalable, so the one true solution for me was to pass the "fixed" or the "fluid" object (returned by a graphQL query for an image), as context to the dynamically created pages, using the createPages API. The solution: to problem 2. Awaiting creation of nodes in sourceNodes, makes createPages trigger only after the images are fetched and nodes are created. Otherwise the image nodes are not available at build time, because they are not yet downloaded.
Then simply add this to any page, created by createPages
And you get the post image. @KyleAMathews, what do you think about this approach? It there a better way to accomplish this? |
Beta Was this translation helpful? Give feedback.
-
It's really sad that optimizing images using Gatsby results in so much overhead and unreadable/unreusable code. We've been so happy after being able to simply While I totally understand that Gatsby has a specific way to handle data and stuff, we should not accept that the most basic things have to be done so complicated. |
Beta Was this translation helpful? Give feedback.
-
@baba43 — no one is accepting anything :-P that's why this issue exists. We'd appreciate your help fixing it. Gatsby is a community built project so needs strong developers like yourself to help improve things. |
Beta Was this translation helpful? Give feedback.
-
It occurs to me that perhaps instead of having to duplicate and generate new queries for every possible variable used within , you could assemble all the possible variables into a list in the query itself. Kind of like Then the compiler just has to recognize the query as a set, and loop over it to generate each copy of the component. It only has to somehow know which component render belongs to which index of the returned set! Just spitballing on that little idea. |
Beta Was this translation helpful? Give feedback.
-
For dynamically querying images, I'm currently simply fetching all nodes and then filtering within the results: import safeGet from 'lodash.get'
import React, { useMemo } from "react"
import { graphql, useStaticQuery } from "gatsby"
import Img from "gatsby-image"
const Image = ({ src, ...props }) => {
const data = useStaticQuery(graphql`
query {
allFile( filter: { internal: { mediaType: { regex: "/image/" } } } ) {
nodes {
relativePath
childImageSharp {
fluid(maxWidth: 300) {
...GatsbyImageSharpFluid_noBase64
}
}
}
}
}
`)
const match = useMemo(() => (
data.allFile.nodes.find(({ relativePath }) => src === relativePath)
), [ data, src ])
const fluid = safeGet(match, 'childImageSharp.fluid')
return fluid ? (
<Img
fluid={fluid}
Tag='div'
{...props}
/>
) : null
}
export default Image |
Beta Was this translation helpful? Give feedback.
-
Regarding the solution of @hiddentao I had to transform it to fit into my setup. I was to lazy to investigate what's exactly the point; might be the environment, versions etc. But heres an apadapted solution. Thanks for your input @hiddentao: import React, { useMemo } from 'react'
import { graphql, useStaticQuery } from 'gatsby'
import Img from 'gatsby-image'
const Image = ({ src, ...props }) => {
const data = useStaticQuery(graphql`
query {
allFile( filter: { internal: { mediaType: { regex: "images/" } } } ) {
edges {
node {
relativePath
childImageSharp {
fluid(maxWidth: 300) {
...GatsbyImageSharpFluid
}
}
}
}
}
}
`)
const match = useMemo(() => (
data.allFile.edges.find(({ node }) => src === node.relativePath)
), [ data, src ])
return (
<Img
fluid={match.node.childImageSharp.fluid}
{...props}
/>
)
}
export default Image |
Beta Was this translation helpful? Give feedback.
-
hi , |
Beta Was this translation helpful? Give feedback.
-
We had an internal discussion around the useQuery prototype and this experiment brought to light reasons that led us to deciding not ship this in Gatsby
This issue is still open because we’re exploring alternative approaches to building an Img component that takes a url! Thank you for your positive comments, folks 🙂 |
Beta Was this translation helpful? Give feedback.
-
@sidharthachatterjee Thank you for giving this the attention! I wanted to comment and say thank you for being so courteous and conscientious in this thread! If you have more details on the internal discussion and can expand upon it, that may be worth citing. If there was a blog post exploring the pros / cons / complexities of this.
Looking forward to this! I made good timing as this was exactly what I was looking into :) |
Beta Was this translation helpful? Give feedback.
-
If there is a preference not to ship this with core, could it be packaged as a Gatsby plugin. Its usage will prove or disprove how much this functionality is needed, and people will undoubtedly contribute further improvements. |
Beta Was this translation helpful? Give feedback.
-
Please consider that this functionality is not only needed for an |
Beta Was this translation helpful? Give feedback.
-
@sidharthachatterjee Out of curiosity: are you able to comment about whether this will be reevaluated when React's |
Beta Was this translation helpful? Give feedback.
-
Maybe we could activate |
Beta Was this translation helpful? Give feedback.
-
Similar views to @jdahdah the following seems quite common from my experiences but is currently impossible:
The only working solution I have at the moment is to query all labels as a page query and prop drill them (or use context). It massively erodes the value of colocated static queries. |
Beta Was this translation helpful? Give feedback.
-
@herecydev If your locale is known to the page (i.e. in the page context) you could use fragments instead. Fragments can access the arguments of the query they are in. Obviously not a pretty solution. |
Beta Was this translation helpful? Give feedback.
-
@hiddentao @nextlevelshit An alternate for when trying to build a dynamic images component is to attach the imageFile to the object on the data level, rather than when trying to render. Doing the match in the image component starts to throw errors when working on a project with a lot of images. src/gatsby-api/create-resolvers/index.js const resolvers = {
AWSAppSync_Product: {
imageFile: {
type: 'File',
resolve: async (source, args, context, info) => {
const node = await context.nodeModel.runQuery({
query: {
filter: {
Key: { eq: source.image1 }
}
},
type: 'S3Object',
firstOnly: true
});
if (node && node.imageFile) return node.imageFile;
}
},
},
}
module.exports = {
resolvers
} gatsby-node.js exports.createResolvers = async ({ createResolvers }) => {
createResolvers(resolvers)
} src/components/image/index.js import React from 'react'
import Img from 'gatsby-image'
export const Image = props => {
if (props.imageFile && props.imageFile.childImageSharp && props.imageFile.childImageSharp.fluid) {
return <Img className={props.imgClassName} alt={props.alt} fluid={props.imageFile.childImageSharp.fluid} />;
}
}; Then use it like: <Image
imageFile={product.imageFile}
alt=""
/>
|
Beta Was this translation helpful? Give feedback.
-
Similar issue here, though I tend to see it the other way around… Our case is an ecommerce website, where product pages includes snippets for some related products (let's say recommandations…) Conceptually, what we would like to have is this:
This is just an example of situations we really have. The general approach to this problem in Gatsby is to fetch all data in the page level query, which is much more efficient, at the cost of some extra complexity, since page-level queries must now be aware of all informations required by all components down the way (fragments may help with this), and these information must passed down to every components along the way. However, in some situations, obtaining these informations from the page level query is simply not possible, or least, not without introducing custom sources, node transformations, or complex schema gymnastic. For example, suppose that product listing and descriptions comes from a general CMS system, product recommendations comes from some business intelligence software, and prices comes from the ERP. Now we have three distinct systems, each with its own gatsby source that needs to be queries recursively... So simple conceptually, but right now, you must be a Gatsby power user to implements this... Now, this might seems naive, but wouldn't it be possible for Gatsby to 1) run these component-level queries during SSR/build, then 2) append the result of that query inside the page-data of the page currently being rendered, and 3) finally, replace that query level component by an access to the corresponding result in the current page-data? Note that this does not adds up to full dynamic component-level queries (which, anyway, requires either server-side support or that full data be available client-side, which is not scalable). Still, it seems to me that it would provide build-time resolution of component-level queries based on contextual variables. Here's how I imagine this, based on my limited knowledge of Gatsby's internal.
Note that eventually, the community could take advantage of both the fact that As I said before, this is probably naive and I certainly miss a lot of details required to make it real, but it seems to me that it is possible to make progress in that direction, and that it would be highly benefical for lots of Gatsby users. |
Beta Was this translation helpful? Give feedback.
-
The images use case for this is now mostly solved by the new StaticImage component. Take a look at the RFC and give it a try: #27950 |
Beta Was this translation helpful? Give feedback.
-
Hi all, wanted to contribute another use case that was similar to what @mjameswh discussed, but only using one data source / cms:
Examples:
The additional options on both of these use cases doesn't fit nicely into the existing build-time data fetching methodology, since data at build time is only ever fetched once. For this to work, we'd need to be able to run an initial query (e.g. existing page queries and fragments), then be able to trigger a second round of queries (possibly static queries at the component level?) based on the initial data that we receive back. The key here, at least to me, is to be able to do all of this within the build step in order to preserve performance benefits. |
Beta Was this translation helpful? Give feedback.
-
My working conclusion is that what we want is inconsistent with
foundational architectural decisions made in the design of Gatsby. I
certainly don't plan to spend any more time pursuing it. --Kevin
…On Fri, May 27, 2022 at 8:51 PM John Dunning ***@***.***> wrote:
Alas, I am now among the broken-hearted.
2.5 years later, me, too. Hard to believe there isn't a solution for this
yet, as I've run smack into it without doing anything complicated. I'm just
trying to pull some static text from a headless CMS at build time, and
wanted to create a simple parameterized component to avoid copying and
pasting the same code over and over. It really feels like I'm either
completely misunderstanding how to use Gatsby, or its popularity is
massively unfounded.
—
Reply to this email directly, view it on GitHub
<#10482 (reply in thread)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAVEX5MJM7ME7LMEJM3FZX3VMFUY5ANCNFSM4TP73BVQ>
.
You are receiving this because you commented.Message ID:
***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
For anyone looking for an implementation with import { graphql, useStaticQuery } from 'gatsby';
import { GatsbyImage } from 'gatsby-plugin-image';
import { useMemo } from 'react';
import React from 'react';
const Image = ({ alt, src }: ImageProps) => {
const data = useStaticQuery(graphql`
query getAllImages {
allFile(filter: { internal: { mediaType: { regex: "/image/" } } }) {
nodes {
relativePath
childImageSharp {
gatsbyImageData
}
}
}
}
`);
const matchedImage = useMemo(
() => data.allFile.nodes.find(({ relativePath }) => src === relativePath),
[data, src],
);
const image = matchedImage?.childImageSharp?.gatsbyImageData;
return !!image ? <GatsbyImage alt={alt} image={image} /> : null;
};
export default Image; |
Beta Was this translation helpful? Give feedback.
-
Update
You now can use
gatsby-plugin-image
that ships with aStaticImage
component: https://www.gatsbyjs.com/plugins/gatsby-plugin-image/Original
In order to run queries for Gatsby, we have to know at build-time what the query is and any arguments.
We can do this with page queries as pages can be created with "context" which can be used as arguments for the GraphQL query.
I didn't allow arguments with
<StaticQuery>
because there wasn't a clear way to allow people to specify build-time arguments that wouldn't confuse people into thinking that arguments would also work at runtime (explaining the distinction between build vs. run time is very hard IME).There also wasn't need for arguments either as the query is only run once — arguments are of course for when you want to run something multiple times but vary in specified ways how the query runs.
But a recent tweet inspired a thought for how arguments could work: https://twitter.com/wesbos/status/1073338618897448960
The tweet's example was about how to create an image component that queries for image thumbnails for gatsby-image using
<StaticQuery>
. The idea is that you'd use it by passing a prop in for the image file you want to use e.g.<MyImage src="kyle-mathews.jpg" />
.my-image.jsx
might look like:Now this by itself wouldn't work. At build time, we'd have no idea about how or where the component is used and what the arguments are and no way to know when and where to load in the results of the queries (how ever many there'd need to be).
But what could work is that we'd detect that a
<StaticQuery>
is taking arguments and then scan for every location that component is used. Then we'd rewrite each usage of<MyImage>
to import a copy ofmy-image.jsx
that has the arguments inserted e.g.<MyImage src="kyle-mathews.jpg" />
gets turned into<MyImage2344324 src="kyle-mathews.jpg" />
my-image-2344324.jsx
looks like:The problems with this are we'd still have to enforce that only static values would be supported as args (strings/numbers) and any run-time data would be ignored. Perhaps we could lint for this.
Dynamic runtime lookup of query results might be supportable by creating a lookup table and then running queries for all possible arguments but this obviously could lead to a crap ton of extra queries being run during builds.
We could use this to ship a nice wrapper to gatsby-image e.g.
<FixedImage>
and<FluidImage>
that would work like:<FixedImage src="kyle-mathews.jpg" width={200} height={300} />
Beta Was this translation helpful? Give feedback.
All reactions