GatsbyJS passing user input to GraphQL - graphql

I’m looking for examples / tutorials on accepting user input from a form in GatsbyJS and passing that to my GraphQL query.
I can get the user input on submit and also pass variables in when testing graphiql, I just can’t figure out how to combine the two.
My data is stored in Drupal and is a list of recipes.
I’d like the user to be able to type in an ingredient e.g. chicken and then retrieve all of the recipes where chicken is an ingredient.
My query is
query SearchPageQuery($ingredient: String) {
allNodeRecipes(filter: {relationships: {field_ingredients: {elemMatch: {title: {eq: $ingredient}}}}}) {
edges {
node {
id
title
path {
alias
}
relationships {
field_ingredients {
title
}
}
}
}
}
}

If I’m understanding your question correctly, the short answer is you can’t, but another approach might work for you.
Gatsby’s GraphQL queries are run in advance as part of the static build of the site, so the data is part of the client-side JavaScript, but the queries have already been run by that point.
This is the same reason you can’t use JavaScript template literals in a StaticQuery:
// This doesn’t work
let myDynamicSlug = 'home'
return (
<StaticQuery
query={graphql`
query ExampleQuery {
examplePage(slug: { eq: ${myDynamicSlug} }) {
title
}
}
`}
render={data => {
console.log(data)
}}
/>
)
You’ll get an error message explaining “String interpolations are not allowed in graphql fragments.” Further reading: https://github.com/gatsbyjs/gatsby/issues/2293
I had a similar problem recently, and I realised it made a lot of sense why you can’t do this. If you are, ex. generating images using the queries in your GraphQL and things akin to that, you can’t pass in client side variables, because all the “static site” Gatsby operations like handling the images have are already done by that time.
What worked for me was to get the larger portion of data I needed in my query, and find what I needed within. In my previous example, that might mean getting allExamplePages instead of one examplePage, and then finding the myDynamicSlug I needed within it:
// This isn’t exactly how you’d hope to be able to do it,
// but it does work for certain problems
let myDynamicSlug = 'home'
return (
<StaticQuery
query={graphql`
query ExampleQuery {
# You might still be able to limit this query, ex. if you know your item
# is within the last 10 items or you don’t need any items before a certain date,
# but if not you might need to query everything
allExamplePages() {
edges {
node {
title
slug
}
}
}
}
`}
render={data => {
// Find and use the item you want, however is appropriate here
data.edges.forEach(item => {
if (item.node.slug === myDynamicSlug) {
console.log(item)
}
})
}}
/>
)
In your case, that hopefully there is an equivalent, ex. looking something up based on the user input. If you can be more specific about the structure of your data, I’d be happy to try and make my suggestion more specific. Hope that helps!

Related

GraphQL "all" in array query?

I'm trying to build a GraphQL query within Strapi (probably not relevant) and I'm not sure how to achieve what I want. So far I've got the following, which is close, but name_in isn't quite what I want.
query {
events(where: { audiences: { name_in: ["Year 1", "Biology"] } }) {
name
audiences {
name
}
}
}
What I'm trying to achieve is events where all of the audience names overlap with the provided query which I ultimately wish to parameterise. Here _in is obviously doing an any. So I only want records with ["Year 1"], ["Biology"] or ["Year 1", "Biology"]. Anything else such as ["Year 2", "Biology"] should not be returned as the complete set of audiences doesn't completely overlap.
Is this possible with vanilla GraphQL or do I need to start write custom resolvers?

How are arguments added to GraphQL, do they need to be defined before?

Hi Everyone I am just trying to learn graphql as I am using Gatsby. I want to know does each field in graphql take an argument or does it need to be defined somehow before. So for example if you visit this link graphql search results
https://graphql.org/swapi-graphql?query=%7B%0A%09allPeople%20%7B%0A%09%20%20people%20%7B%0A%09%20%20%20%20id%0A%20%20%20%20%20%20name%0A%20%20%20%20%20%20birthYear%0A%20%20%20%20%20%20eyeColor%0A%09%20%20%7D%0A%09%7D%0A%7D%0A
If i wanted to limit people by eye color how would I do that. In the docs it seems easy as you would just do something like people(eyecolor: 'brown') but that doesn't seem possible. Am I missing something? I basically want to do a SQL style search for all people where eye color is brown.
Thanks.
Arguments need to be defined in the schema and implemented in the resolver. If you're consuming a 3rd party API (like the link you provided), you're limited to their schema. You can tell by looking at their schema (by clicking Docs on the right side of the page) which fields take arguments. For example, person takes id and personID arguments:
people doesn't take any arguments, as seen in the schema:
If you're building your own schema, you can add arguments to any field, and when you implement the resolver for that field you can use the arguments for logic in that resolver.
If you're working with a schema that you don't control, you'll have to add filtering on the frontend:
const {people} = data.allPeople;
const brownEyedPeople = people.filter(({eyeColor}) => eyeColor === 'brown');
When you start developing in Gatsby and actually pull your data into Gatsby, there will be a filter query option that automatically becomes available in the query arguments.
https://www.gatsbyjs.org/docs/graphql-reference/#filter
You can expect to be able to filter your people by eyeColor by using the below query:
{
allPeople(filter: { eyeColor: { eq: "brown" } }) {
edges {
node {
id
name
birthYear
eyeColor
}
}
}
}

GraphQL retrieve data for specific blocks - Gatsby + Wordpress

I have a React + Gatsby JS project that retrieves data from a Wordpress site through their headless API. I'm a total newbie to Gatsby.
Every page on my site is made up of blocks, which are in turn made up of fields. I'm using ACF to build these.
I am currently able to retrieve every page and a list of the blocks within that page by using the following GraphQL query:
query ($id: String!) {
currentPage: wordpressPage(id: {eq: $id}) {
title
acf {
page_blocks {
block_type {
acf_fc_layout
}
}
}
}
}
This returns the following data for page with id f4c4f4a7-ba0d-55b1-8877-16f543c22b80
{
"data": {
"wordpressPage": {
"id": "f4c4f4a7-ba0d-55b1-8877-16f543c22b80",
"acf": {
"page_blocks": [
{
"block_type": [
{
"acf_fc_layout": "page_title_and_text"
},
{
"acf_fc_layout": "two_column_media_and_text"
}
]
}
]
}
}
}
}
The blocks are next to afc_fc_layout. Both page_title_and_text and two_column_media_and_text are page blocks in that page.
Now, I would think that the next step would be to make a React component for each of those blocks, passing in the custom field data for each, to that component. If a page doesn't have a block, then there wouldn't be a need for me to retrieve the fields for that block, right?
Initially I thought I would run another query from my React component, requesting the fields for that particular block. But I realized I can't really add variables (page Id) to a static query within my components, per Gatsby docs, so I wouldn't be able to query that specific page for its fields. Please correct me if I'm wrong.
I believe I have to retrieve those fields I need from my main query that I've shown you here, but it seems absolutely bonkers to have to query for every possible custom field on the site, when not all pages are going to have the same blocks.
Ideally there would be some sort of syntax like
...
acf {
page_blocks {
block_type {
acf_fc_layout
if (acf_fc_layout eq page_title_and_text) {
title
text
}
if (acf_fc_layout eq two_column_media_and_text) {
media
text
}
}
}
}
...
And then I would pass those fields to their corresponding React component.
What is the proper way to go about this?
Note: I am currently at the point where I'm able to retrieve the fields from the API to render blocks. I am more wondering if there is any way my graphQL query can filter out the data for me, or if there is a way to customize the WP endpoint to show me field data filtered by the blocks that are actually on the page.
Ex: the site queries the data in blocks 4,3,2,10,12,15.... even though the page only has block 2.
I'm worried that devs that want to add blocks in the future will have to rewrite the query each time, hurting the site's scalability and potential performance.
You say you are a beginner with Gatsby but what you are trying to do touches many advanced topics inside Gatsby. My answer is most likely incomplete and you will need to figure many things out for yourself.
Prepare yourself for lots of documentation reading and lots of debugging to get things to work with Gatsby.
You want to programmatically create pages depending on the result of your GraphQL query. That means you need to create a page wide page template component.
In your templates folder of your Gatsby project, you create one template that programmatically picks the right components for each of your routes. To get your ACF data you use GraphQL page queries.
What is the proper way to go about this?
One alternative is this: You create React components that retrieve their data via props. You don't need to give each of those components their own GraphQL query since you already query in your page templates.
acf: acf_fc_layout eq page_title_and_text -> React component PageTitleAndText.jsx
const PageTitleAndText = ({ title, text}) => {
return (
<div>
<h1>{title}</h1>
<p>{text}</p>
</div>
);
};
// NO GraphQL query
export default PageTitleAndText;
Instead, you pass props inside your page template to your component:
acfPageTemplate.jsx
const acfPageTemplate = (props) => {
return (
<div>
{/* pass props as data from the GraphQL query result here */}
<PageTitleAndText title={props.data.currentPage.acf.page_blocks.block_type.acf_fc_layout.title }
text ={props.data.currentPage.acf.page_blocks.block_type.acf_fc_layout.text} />
</div>
);
};
export const query = graphql`
query ($id: String!) {
currentPage: wordpressPage(id: {eq: $id}) {
title
acf {
page_blocks {
block_type {
acf_fc_layout
}
}
}
}
}
`;
export default acfPageTemplate;
Define a page template for each of your acf layouts. Pick the right components for each layout and pass props as data from the GraphQL query result.
You need to pass variables to your page query. The only way to do this is to use page context as described in this question:
gatsby-node.js
createPage({
path: `/my-acf-page-title-and-text-page/`,
component: path.resolve(`./src/templates/PageTitleAndText.jsx`),
// The context is passed as props to the component as well
// as into the component's GraphQL query.
context: {
id: acfFieldId, // pass the acf field id
},
})
// define a createPage action for each of your acf layouts
But I realized I can't really add variables (page Id) to a static query within my components, per Gatsby docs, so I wouldn't be able to query that specific page for its fields. Please correct me if I'm wrong.
Correct. That's why you need to go the way with a page query, page template, and page context variable in gatsby-node.js
If a page doesn't have a block, then there wouldn't be a need for me to retrieve the fields for that block, right?
Yes. That's why you create a different page template for each of your acf layouts. You can create one big tempalte for all layouts but then you need to programmatically decide what components to add. This is out of scope of this question. You should ask a new question if you want to do this.
My advise is to get this to work with one specific layout before you go down this next rabbit hole, if you decide to do this at all.

How can i define arguments for nested fields on server side using grapqhl?

How can I define arguments for nested fields? Suppose I want to consult all my posts but limit and sort the comments. Thank you for your help.
{
allPosts {
title,
comments(limit: 5) {
content
}
}
}
What you are referring to, is often designated as Pagination, and is something that is covered by the GraphQL's specification.
There are different possible ways of constructing the query to allow retrieving multiple records of a certain object type (comments in our situation).
The simplest option, can be achieved by defining the GraphQL's query string with the object type you want to transverse in the plural form, meaning that your query would look like this:
{
allPosts {
title,
comments {
content
}
}
}
But with this implementation you would end up fetching all the data instead of simply retrieving a chunk of it. Obviously this approach can have many drawbacks depending on the volume of the data that is being fetched and should only be used on specific situations.
The easiest approach to achieve what you want is to request the comments as a "slice", meaning that you would be requesting a specific initial portion of the data set.
In this case, you would be requesting the initial 5 comments.
{
allPosts {
title,
comments(first: 5) {
content
}
}
}
But what if you want to paginate through the rest of the list?
{
allPosts {
title,
comments(first: 5, offset:5) {
content
}
}
}
Doing this, you could ask for the following next 5 comments.
But the approach that is recommend to use when implementing Pagination is the cursor-based pagination, which would translate to something like this:
{
allPosts {
title,
comments(first: 5) {
edges {
node {
content
}
cursor
}
}
}
}
The hard part consists on implementing the resolvers functionality (is slightly easier with frameworks like Apollo).

Show data from Prismic.io in GatsbyJS frontend

I was playing around with prismic.io as a content source for a gatsby site and just can't figure out why I can't output certain data.
The graphql tool from gatsby returns all the right data so the query itself seems fine:
export const pageQuery = graphql`
query PageQuery {
allPrismicDocument {
edges {
node {
data{
product_image {
url
}
product_name {
text
}
product_price
product_description {
text
}
}
}
}
}
}
`
Now inside the page, when adding values such as:
{node.data.product_price}
{node.data.product_image.url}
it works just fine and outputs the correct data. However when I try:
{node.data.product_name.text}
or
{node.data.product_name}
I get nothing at all. Searched everywhere but there aren't many resources for using these two tools together yet:/
Any pointers would be much appreciated :)
I'm guessing that your product_name field is a Prismic Rich Text or Title field. If that's the case, then the field is an array of text blocks. You have two options for this:
Use the prismic-react development kit to help you display the field. Here is the Prismic documentation for this. This shows you how to use the RichText.render() and RichText.asText() helper functions to display this field type on a React front-end (which should work for Gatsby as well). It would look something like this:
{RichText.asText(node.data.product_name)}
If the field only has one block of text, you could just grab the first element of the array. Like this:
{node.data.product_name[0].text}

Resources