How to workaround non existent types in Graphql query in Gatsby - graphql

I'm building a website with a blog section, and on deployment to production the blog will be empty. I'm having problems allowing an empty blog on my Gatsby site.
When I run npm run develop it will only work if I have some blogs - I want it to work before the blogs have been added.
The main issues I'm encountering is trying to accomidate fields not existing like allStrapiBlog and strapiBlog because there are no blogs.
I get errors like this on blog components, and on my nav component (where i have a query that a conditional uses).
15:17 error Cannot query field "allStrapiBlog" on type "Query" graphql/template-strings
Cannot query field "strapiBlog" on type "Query"
This is what the query looks like for my navigation component. But it throws an error - is there a way to make it just return null?
query NavigationQuery {
allStrapiBlog {
nodes {
title
strapi_id
}
totalCount
}
}
How do I make unsuccessful GraphQL queries not break the build, so I can build a gatsby site with a empty blog?

But it throws an error - is there a way to make it just return null?
Indeed, you need to configure your GraphQL schema to allow nullable fields.
You have a boilerplate that you can tweak to match your data types at https://www.virtualbadge.io/blog-articles/nullable-relational-fields-strapi-gatsbyjs-graphql.
The idea relies on using the createSchemaCustomization API in your gatsbt-node.js to add your own type definitions.
Something like:
exports.createSchemaCustomization = ({ actions }) => {
const { createTypes } = actions;
const typeDefs = `
type StrapiBlogPost implements Node {
title: String!
content: String
thumbnail: File
}
`;
createTypes(typeDefs);
};
In this case, the title is required (because of the !, which means that the type is non-nullable) while content and thumbnail can be null.
Afterward, you will only need to adapt your component to avoid code-breaking logics when null data is fetched.

Related

Should a query in Apollo Client look for the results cached by different queries before making a network request?

I'm trying to figure out how queries in Apollo Client are supposed to interact with the cache.
Specifically, I want to know if we run a query that fetches all todos:
todos {
title
completed
}
And then later we run a query that fetches a single todo that was already fetched by the todos query and requests the exact same fields:
todo(id: $id) {
title
completed
}
Should the second query a) fetch the data from the cache, or b) make a network request?
My assumption was that it would be case A. This is based on this quote from an official Apollo blog post:
https://www.apollographql.com/blog/demystifying-cache-normalization/
For example, if we were to:
Perform a GetAllTodos query, normalizing and caching all todos from a backend
Call GetTodoById on a todo that we had already retrieved with GetAllTodos
...then Apollo Client could just reach into the cache and get the object directly without making another request.
However, in my app I kept getting case B, it was always making an additional network request even though I had already requested all the data in a different query.
I assumed that I was doing something wrong, so I checked out this Apollo Full-stack Tutorial repo (https://github.com/apollographql/fullstack-tutorial) and updated the LaunchDetails query to only request the same data that was already requested in the GetLaunchList query. This replicated the same scenario I detailed above with the todos.
The queries now look like this:
export const GET_LAUNCHES = gql`
query GetLaunchList($after: String) {
launches(after: $after) {
cursor
hasMore
launches {
...LaunchTile
}
}
}
${LAUNCH_TILE_DATA}
`;
export const GET_LAUNCH_DETAILS = gql`
query LaunchDetails($launchId: ID!) {
launch(id: $launchId) {
...LaunchTile
}
}
${LAUNCH_TILE_DATA}
`;
I ran the application, and found that a new network request was made for the LaunchDetails query, even though all the required data was already in the cache after the GetLaunchList query was run.
I haven't been able to find any answer to this in the documentation, and the results I'm seeing from the example tutorial app seem to be at odds with the quote from the blog piece above.
Is it the case that a query will only look to the cache if the query has already been run before? Can it not fetch cached data if that data was cached by a different query? Am I missing something?
Please see this better (in my opinion) answer here:
https://stackoverflow.com/a/66053242/6423036
Copying directly from that answer, credit to the author:
This functionality exists, but it's hard to find if you don't know what you're looking for. In Apollo Client v2 you're looking for cache redirect functionality, in Apollo Client v3 this is replaced by type policies / field read policies (v3 docs).
Apollo doesn't 'know' your GraphQL schema and that makes it easy to set up and work with in day-to-day usage. However, this implies that given some query (e.g. getBooks) it doesn't know what the result type is going to be upfront. It does know it afterwards, as long as the __typename's are enabled. This is the default behaviour and is needed for normalized caching.
Let's assume you have a getBooks query that fetches a list of Books. If you inspect the cache after this request is finished using Apollo devtools, you should find the books in the cache using the Book:123 key in which Book is the typename and 123 is the id. If it exists (and is queried!) the id field is used as identifier for the cache. If your id field has another name, you can use the typePolicies of the cache to inform Apollo InMemoryCache about this field.
If you've set this up and you run a getBook query afterwards, using some id as input, you will not get any cached data. The reason is as described before: Apollo doesn't know upfront which type this query is going to return.
So in Apollo v2 you would use a cacheRedirect to 'redirect' Apollo to the right cache:
cacheRedirects: {
Query: {
getBook(_, args, { getCacheKey }) {
return getCacheKey({
__typename: 'Book',
id: args.id,
});
}
},
},
(args.id should be replaced by another identifier if you have specified another key in the typePolicy)
When using Apollo v3, you need a typepolicy / field read policy:
typePolicies: {
Query: {
fields: {
getBook(_, { args, toReference }) {
return toReference({
__typename: 'Book',
id: args.id,
});
}
}
}
}
the query will make a network query.
todo(id: $id) {
title
completed
}
Apollo cache isn't very smart. It is just storage. You need to read/write for more complicated operations manually.
The reason for this is Apollo doesn't know about your schema and data structure. It doesn't know that todo(id: $id) will do DB search by, so it can't optimize to look in the cache.
If you don't want a second fetch, you have to implement your data fetch structure with fragment:
try {
return client.readFragment({
id: 'Todo:5', // The value of the to-do item's unique identifier
fragment: gql`
fragment TodoFragment on Todo {
id
title
completed
}
`,
});
} catch(_e) { // if no fragment is found there will be an error
client.query(QUERY, variables: { id: 5})
}
The way Apollo cache is that if you do two queries:
load todos
todos {
id
title
completed
}
load single todo
todo(id: $id) {
id
title
completed
}
If you list a list of todos and load the second one - it will update the todo data.

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.

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}

Is there a way to pass a fragment to graphiql?

It's possible to pass a query, but apparently not a fragment:
server.use('/graphiql', graphiqlExpress({
endpointURL: '/graphql',
query: `# Welcome to GraphiQL
query PostsForAuthor {
author(id: 1) {
firstName
posts {
title
votes
}
}
}`}));
Update 10/12/2017
It is possible to send fragments along with a query using Apollo's client:
http://dev.apollodata.com/core/fragments.html
This is not a solution to the original question, however; I would like to pass fragments to a graphiql server instance at startup.
by startup do you mean from the server? if so I don't believe that's how fragments are used. my understanding is as follows:
on the server you provide Types (like User)
on the client you query those Types using queries and fragments
for instance, if you provide type User on the server, on the client graphQL you can use fragments to query that type:
graphQL (client)
fragment authorData on AuthorType{
firstName
posts {
title
votes
}
}
query PostsForAuthor {
author(id: 1) {
...authorData
}
}
As you noticed (and as detailed here) GraphiQL takes a query argument:
query: an optional GraphQL string to use as the initial displayed query, if undefined is provided, the stored query or defaultQuery will be used.
If putting a fragment in as the value for that argument doesn't work, then I don't believe there is any way to start with a fragment ... but really why would you even want to? A fragment by itself isn't executable, and the whole idea is to start GraphiQL with a (executable) query.
If all you want is to be able to copy/paste in some text that you use frequently in your queries, a bookmarklet might be a better idea.

Resources