According to docs (at
https://github.com/gatsbyjs/gatsby/blob/master/docs/docs/static-query.md#how-staticquery-differs-from-page-query):
StaticQuery does not accept variables (hence the name "static"), but can be used in any component, including pages
However it could be very useful to add yet additional attribute to pass variables into the query.
Or may be add DynamicQuery to use queries inside the components with the variables.
The variables attribute may contain single default variable, array of variables or map with varName:varValue pairs.
export default props => (
<StaticQuery
variables={this.props.pageContext.slug}
query={
`query ArticleBySlugEn( $slug: String! ) {
markdownRemark( fields: { slug: { eq: $slug } } ) {
html
frontmatter {
title
date
}
fields {
slug
}
}
}
`}
render={data => <Header data={data} {...props} />}
/>
);
I have multi language site with markdown pages. I need one common template for all pages. It is a component with a query. This query has a variable (slug). And I have page templates for each used language which I want to maintain as little as possible. But due to limitations of Gatsby 2.0 to query with variables from the components I just found I need to move query up and clone it into each of my top level language specific page template.
It is working somehow but I see warning on the console like this:
The GraphQL query in the non-page component "/path/to/ArticleTemplate.js" will not be run.
Exported queries are only executed for Page components. Instead of an exported
query, either co-locate a GraphQL fragment and compose that fragment into the
query (or other fragment) of the top-level page that renders this component, or
use ain this component. For more info on fragments and
composition, see http://graphql.org/learn/queries/#fragments and for more
information on, see https://gatsbyjs.org/docs/static-query
Probably I can reduce boilerplate using the fragments. But anyway I have to put some query code into each language specific top(page) component.
I prefer to forward the this.props.pageContext into my common template component and use it in StaticQuery.
@engilyin at the moment, StaticQuery isn't intended to accept GraphQL arguments and adding that functionality isn't on our roadmap. We'd be happy to see a pull request if someone wants to tackle this.
/cc @jlengstorf
@engilyin I agree that this would be really useful! However, the challenge is in the way StaticQuery
is evaluated.
During the build, Gatsby looks for instances of StaticQuery
in the AST and executes those queries in place. By accepting variables, we would need to trace the variables back to where they're set so we could pull those values out for evaluation, which is a non-trivial amount of effort.
To reiterate @kakadiadarpan, we'd absolutely love to get a pull request to implement this feature if someone has time to tackle it.
@kakadiadarpan and @jlengstorf thank you for a quick response! I just get started to use Gatsby and its internals are not so clear for me to help with pull request at this level.
However what about another idea to create DynamicQuery and execute it when variables are known?
Also there is an idea to define some global(page-wide) variables which would be know before you will process StaticQueries. E.g., when we use gatsby-node.js and createPage we may define context and its variables are used for global queries. Are you calling the execution of StaticQueries before or after I call createPage? If after(and I believe that) the context variables should be already easily available? Don't it?
I'm sorry if my ideas looks strange I previously used mostly Java JSF so could simply have misconceptions about how works ReactJS/Gatsby component model.
We could add a dynamic query to pages, but components are trickier. The benefit of StaticQuery
is that it can be used anywhere, not just in pages.
Pages make it more predictable for us where the variables are set; with dynamic queries in components it's harder to determine where that data came from.
However, it's definitely possible; just not under active development right now. PRs welcome!
on the create pages API how can you do below if youre only allowed to use StaticQuery and it cant read the context.
).then(result => {
result.data.allMarkdownRemark.edges.forEach(({ node }) => {
createPage({
path: node.fields.slug,
component: path.resolve(`./src/templates/blog-post.js`),
context: {
// Data passed to context is available
// in page queries as GraphQL variables.
slug: node.fields.slug,
},
})
})
resolve()
})
})
@ekdev2 You can use standard queries with context on pages. If you need to access context, use a page query.
StaticQuery
isn't a requirement, and you're allowed to use either page queries or StaticQuery
in any combination.
Does that make sense?
@jlengstorf thanks for the response. On this section https://www.gatsbyjs.org/docs/static-query/#how-staticquery-differs-from-page-query it says "page queries can accept variables (via pageContext) but can only be added to page components". So in the section where I create pages programmatically (gatsby-node), can I still use components that reside in /src/templates ie. /src/templates/blog-post.js and that be considered a page component?
@ekdev2 Yes! If you supply a component to createPage
, it treats that component as a page component. Under the hood, Gatsby is effectively calling createPage
for each component in the src/pages/
directory and doing exactly that.
Hope that helps!
What's the best practice in the end when I need to pass a variable to the GraphQL query (that's only applicable to a page query and not StaticQuery as I understand it) but the command line warning says I should use StaticQuery or co-located GraphQL fragments. I guess then there is no way around fragments to avoid the warning for dynamically created pages?
@rwieruch the warning should be suppressed on dynamic pages. If the createPage
call uses the component with the page query, it should work without warnings.
If that's what you're doing, could you share the code that's producing the warnings so we can take a look?
Thanks!
EDIT: I think in the case of my issue it may be MDX related.
@jlengstorf thanks for your reply. Yes, that's why I was wondering about the warning. I get it for one of my templates:
The GraphQL query in the non-page component "/Users/rwieruch/Developer/Blogs/rwieruch/src/templates/post.js" will not be run.
Exported queries are only executed for Page components. Instead of an exported
query, either co-locate a GraphQL fragment and compose that fragment into the
query (or other fragment) of the top-level page that renders this component, or
use a <StaticQuery> in this component.
As the warning says, the file sits in the src/templates/post.js
folder. I create the post pages in the gatsby-node.js
file:
createPage({
path: node.fields.slug,
component: componentWithMDXScope(
path.resolve(`./src/templates/post.js`),
node.code.scope,
__dirname,
),
});
I guess, the only difference from this template compared to the other templates is the MDX, because I don't get the warning for my other dynamic pages.
Further Observations:
Even though the warning shows up, it works with the MDX when defining the page query as the following for the dynamic page:
export const pageQuery = graphql`
query($id: String!) {
site {
siteMetadata {
siteUrl
}
}
post: mdx(fields: { id: { eq: $id } }) {
frontmatter {
title
}
}
}
`;
However, if I change the variable name (e.g. from pageQuery
to query
) it will not work, because I never receive the data
prop in props
in my component. Also when I give the query a query name (e.g. query Post($id: String!) {
) I will get an error in the command line even though the query name wasn't used somewhere else:
error GraphQL Error There was an error while compiling your site's GraphQL queries.
Invariant Violation: GraphQLCompilerContext: Duplicate document named `Post`. GraphQL fragments and roots must have unique names.
t: Duplicate document named `Post`. GraphQL fragments and roots must have unique names.
So maybe the whole MDX in Gatsby is doing some more work under the hood here. But that's just my assumption.
@rwieruch Yep, that's an MDX bug: https://github.com/ChristopherBiscardi/gatsby-mdx/issues/214
It's been fixed (https://github.com/ChristopherBiscardi/gatsby-mdx/pull/221), but it's not released under the gatsby-mdx@latest
tag just yet. In the meantime, you can safely ignore the warning.
The pageQuery
issue is also a known MDX issue (https://github.com/ChristopherBiscardi/gatsby-mdx/issues/203) that's fixed by the same PR above.
MDX is still in early days — please follow that repo and open issues if you hit more MDX trouble!
Thanks!
Thank you @jlengstorf for the thorough answer! Helps me a lot :) Keep up the good work!
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! 💪💜
Just a thought: Wouldn't it be helpful if <StaticQuery />
could use the same variables from pageContext that the page query can? 🤔
You'd need to evaluate each StaticQuery for each page it exists in once.
createPage({
...,
context: {
someVar: 'Foo',
},
})
// warning: this is an imaginary api and not working code
function SomeComponent() {
const data = useStaticQuery(graphql`query ($someVar: String!) { … }`)
...
}
Does this make sense?
Would it be helpful to have this? I feel like it would cover at least some use cases and should be quite a bit simpler to implement than fully dynamic variables.
Having said the above: One thing about Gatsby that super confused me in the beginning (and still is) are the page queries. They somehow magically provide data to a component just by being declared.
Consider this current api:
export default function MyPage({ data }) {
return <div>{data.page.title}</div>;
}
export const query = graphql`query MyPage($id: Int!) { page(id: $id) { title } }`;
When learning this I asked myself:
query
?Now imagine the page query api would not exist and you were to use a static query instead:
// warning: this is an imaginary api and not working code
export default function MyPage() {
const data = useStaticQuery(graphql`query MyPage($id: Int!) { page(id: $id) { title } }`);
return <div>{data.page.title}</div>;
}
This feels a lot more explicit and potentially less confusing to me.
I think it only makes complete sense with a hook though.
I'm sorry if this is a little bit out of scope for this issue - do you think this could qualify as a rfc maybe?
When learning this I asked myself:
- does the query const has to be named
query
?- does it need to be exported?
- does the query has to have the same name as the component?
- how does gatsby get the data prop into my component (since there is no explicit connection besides them being in the same file)?
These are exactly the same things I've been asking to myself. @djfarly did you come closer, didn't you?
@djfarly @nextlevelshit this is good feedback, and definitely something we want to clear up.
To answer those questions:
- does the query const has to be named query?
Nope! It can be named whatever you want.
- does it need to be exported?
No, but exporting it avoids a linter warning.
I was wrong. It _does_ need to be exported.
- does the query has to have the same name as the component?
No. The query doesn’t need a name at all, actually.
- how does gatsby get the data prop into my component (since there is no explicit connection besides them being in the same file)?
We extract the page queries at build time, execute them, and make the result available as the data
prop. This is _only_ done for page components and components used in createPage
calls, so Gatsby is able to make assumptions about how this works (e.g. the query and the component in that file are linked).
That special handling is also what prevents Gatsby from supporting variables in static queries.
For a little more insight into how creating pages works and why we use GraphQL to manage data, I just released a new mini-course on egghead about this: https://egghead.io/playlists/why-gatsby-uses-graphql-1c319a1c
Thanks!
One workaround is to include all files that you gonna use in the component and then provide variable names when you actually access the data.
One example I'm using is a reusable hero image component.
In my hero.js, I have something like:
<StaticQuery
query={graphql`
{
home: file(relativePath: { eq: "heros/home/hero-1.png" }) {
childImageSharp {
fluid(maxWidth: 1600) {
...GatsbyImageSharpFluid
}
}
}
work: file(relativePath: { eq: "heros/work/work.png" }) {
childImageSharp {
fluid(maxWidth: 1600) {
...GatsbyImageSharpFluid
}
}
}
services: file(relativePath: { eq: "heros/services/services.png" }) {
childImageSharp {
fluid(maxWidth: 1600) {
...GatsbyImageSharpFluid
}
}
}
work: file(relativePath: { eq: "heros/work/work.png" }) {
childImageSharp {
fluid(maxWidth: 1600) {
...GatsbyImageSharpFluid
}
}
}
footer: file(relativePath: { eq: "heros/cta.png" }) {
childImageSharp {
fluid(maxWidth: 1600) {
...GatsbyImageSharpFluid
}
}
}
}
`}
render={data => (
<BackgroundImage
tag="section"
fluid={data[heroImg].childImageSharp.fluid}
>
...some other code...
</BackgroundImage>
)}
So in this way, when I want to use the hero component across the entire app, I can pass props to it to specify different images that I want to use for different pages.
For example, I can have heroImg="contact" on my contact page when I use hero component.
<Hero
heroHeightClass="col-12"
heroImg="contact"
/>
In the home page, I can have heroImg="home" to tell my hero component to use hero images for home not others.
This is the only way I think you can "use" props/variables in StaticQuery for now. Correct me if wrong plz.
@ZhenWang-Jen this definitely works (it's how I was tackling the problem for a while), but it comes with a pitfall: the data in that query needs to be loaded on every page. For a few images this won't be a big deal, but as the number of images increases, so does the extra page bloat.
@jlengstorf improvement is possible. Just change the batch file imports query in the hero.js, in my case, to something like below which will loop through ALL images that are in the targeted folder:
query={graphql`
{
allHeroImgs: allFile(
filter: { absolutePath: { regex: "/heros/" } }
) {
edges {
node {
relativePath
childImageSharp {
id
fluid(maxWidth: 1600) {
...GatsbyImageSharpFluid
}
}
}
}
}
}
`}
And then easily change your render to a map through all images queried from above:
render={data =>
data.allHeroImgs.edges.map(({ node }) =>
node.relativePath.indexOf(heroImg + ".png") !== -1 ? (
<div key={node.id}>
<BackgroundImage
tag="section"
fluid={node.childImageSharp.fluid}
>
...some children elements code...
</BackgroundImage>
</div>
) : null
)
}
Finally to actually use it, just keep the heroImg consistent with the hero image file name in your folder:
<Hero
heroHeightClass="section-lg"
heroTitle={'blabla'}
heroImg="about"
/>
this will show your about.png as the hero image for your about page. For other pages, just change heroImg to whatever works for your other pages like heroImg="contact" or heroImg="careers".
Further, refer to the official doc if you have a bunch of images and you want them all to use the same formatting.
Inspired by StackOverflow.
Hope this helps!
Duplicate of https://github.com/gatsbyjs/gatsby/issues/10482
Most helpful comment
Having said the above: One thing about Gatsby that super confused me in the beginning (and still is) are the page queries. They somehow magically provide data to a component just by being declared.
Consider this current api:
When learning this I asked myself:
query
?Now imagine the page query api would not exist and you were to use a static query instead:
This feels a lot more explicit and potentially less confusing to me.
I think it only makes complete sense with a hook though.
I'm sorry if this is a little bit out of scope for this issue - do you think this could qualify as a rfc maybe?