I'm trying to create a blog page that can optionally display any image assets when they exist.
First, I created a content modal in Contentful with a Rich Text named "Body", and then I created an entry with some random text in it. In Gatsby, when I try to query the data via GraphQL explorer, I can query the "raw" field inside the "body". The "references" field can not be queried unless I add at least one image asset (aka, "dummy content").
below is my graphql query
export const query = graphql`
query ($slug: String!) {
contentfulBlog(slug: { eq: $slug }, node_locale: { eq: "en-US" }) {
title
body {
raw
references {
... on ContentfulAsset {
contentful_id
__typename
gatsbyImageData(formats: AUTO, layout: FULL_WIDTH)
}
}
}
}
}
`
The problem with the above query is that if there is no image asset found in a post, it will break the query. And my goal is to be able to optionally query the references field when there are assets available. But I'm not sure how to do it.
**Updated on Aug 24th, 2021:
For anyone that is also struggling with this problem as of today:
After digging through tons of documentation and answers, I've found out that this is in fact one of the limitations of the gatsby-source-contentful plugin. In short, the plugin(v4), as it states in its documentation:
At the moment, fields that do not have at least one populated instance
will not be created in the GraphQL schema.
Not sure if this will be addressed in a future release, but currently, it is required to have at least one dummy content(embedded image, links...etc) added in your rich text body in order for the plugin to create the "references" field in the graphql schema.
Here is the official discussion thread
If the idea is to only query references if they are available you need to customize the GraphQL schema to make the field nullable (meaning it can be empty, null) because, by default, it's inferred that it isn't.
You can check for Contentful content types in: https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/content-types/content-model
This would be the ideal solution. Alternatively, according to this Medium article, you can bypass it by wrapping your offending node within a Node-type object. In your case:
export const query = graphql`
query ($slug: String!) {
contentfulBlog(slug: { eq: $slug }, node_locale: { eq: "en-US" }) {
title
body {
raw
references {
... on Node {
... on ContentfulAsset {
contentful_id
__typename
gatsbyImageData(formats: AUTO, layout: FULL_WIDTH)
}
}
}
}
}
}
`
You may need to tweak it. Without knowing your data and schema structure it's impossible to guess if the query above will work alone, but get the idea. Check it in the GrahiQL playground (localhost:8000/___graphql)
Other resources:
https://github.com/gatsbyjs/gatsby/issues/20696
https://github.com/gatsbyjs/gatsby/pull/20794
Related
It seems, that Keystone.js does't provides a solution of "N+1" problem.
Maybe, they are some plugins for that?
You can try to check https://www.keystonejs.com/guides/cache-hints/
This is a typical pattern for solving this problem.
You can try:
const app = new GraphQLApp({
apollo: {
cacheControl: {
defaultMaxAge: 3600,
},
},
});
to cache all resolver results.
What version are you on? The current version of KeystoneJS (Keystone Next) is build on top of Prisma which should be building fairly performant DB queries. If there's a specific GraphQL query you're preforming that's resulting in a suboptimal SQL it may represent a bug in either Keystone or Prisma code, in which case it'd be great if you could isolate the problem and log an issue.
If you're adding hooks, access control or virtual fields that query the DB it is possible to encounter the N+1 problem as those functions can be called for each item returned in a query. For example, this code, taken from the virtual-fields example causes N+1 queries if used as written:
Post: list({
fields: {
// [... various fields ...]
author: relationship({ ref: 'Author.posts', many: false }),
// A virtual field which uses `item` and `context` to query data.
authorName: virtual({
field: schema.field({
type: schema.String,
async resolve(item, args, context) {
const { author } = await context.lists.Post.findOne({
where: { id: item.id },
query: 'author { name }',
});
return author && author.name;
},
}),
}),
},
}),
Here, the resolver function for the authorName field will be called for each item loaded (assuming the field is queried). In these cases I'd suggest using something like the GraphQL dataloader (or similar) over the top of the Keystone CRUD API. If used correctly, dataloader can combine multiple queries and resolve the N+1 behaviour.
I don't really understand graphql or gatsby that well but I believe all my images are loaded into graphql by putting this in my gatsby-config.js
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: path.join(__dirname, `src/assets/images`),
},
},
I am then trying to query a specific image which I can do with
query MyQuery {
allImageSharp(filter: {id: {eq: "7acfccd5-4aef-532b-8889-9d844ae2068b"}}) {
edges {
node {
sizes {
sizes
srcSet
src
aspectRatio
}
id
}
}
}
}
And this returns what I want, but the id that I have to enter for this query is 7acfccd5-4aef-532b-8889-9d844ae2068b. Is this id even going to stay the same if I put it in my code? Is there a way to set the id to something more sensical?
If I save my query to a variable data, it turns out that on Netlify data.allImageSharp.edges[0] is null, while locally this value will return what I need
I'm looking for the best way to query a single image. Not multiple images.
If I could set my own id's then I could query these.
Update
I found an example in the gatsby-source-filesystem documentation, but don't really know how to add it to my code
With this snippet:
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: path.join(__dirname, `src/assets/images`),
},
},
Basically you are telling your GraphQL schema that will find images in src/assets/images folder so you will be able to query all the content inside this folder via GraphQL. You are specifying the source for your data.
From gatsby-source-filesystem documentation:
The plugin creates File nodes from files. The various “transformer”
plugins can transform File nodes into various other types of data e.g.
gatsby-transformer-json transforms JSON files into JSON data nodes and
gatsby-transformer-remark transforms markdown files into
MarkdownRemark nodes from which you can query an HTML representation
of the markdown.
Answering your question, of course, you can filter and sort for any property or node that your image has, such as name, path, extension, etc. You may find a useful and autocompletion tool for your queries under /___graphql path when you run a gatsby develop command. This will help you to check out what parameters can be queried and filtered.
If you have an image with a path of src/images/exampleImage.jpg, you can query by the images name by querying using file rather than allImageSharp.
query HeaderQuery {
exampleImage: file(
extension: {eq: "jpg"},
name: {eq: "exampleImage"}
) {
childImageSharp {
fluid {
src
srcSet
sizes
srcSetWebp
}
}
}
}
You can also replace {extension: {eq: "jpg"} with {extension: {regex: "/(jpg)|(jpeg)|(png)/"} to query files of any extension type.
I am trying to retrieve data in form of a list from markdown frontmatter using gatsbyjs and graphql. Given the List in the markdown file, the goal would be to fetch the list items into my react component for further use.
This is for my work where I want to create a website using GatsbyJS and React (that's why I can't share real code).
So far I've tried to simply fetch the data via GraphQL from frontmatter but either the field is undefined or just contains null values.
I have a frontmatter like this:
---
name: "someName"
expertise: "someExpertise"
...
technologies:
- tech1
- tech2
- tech3
---
in my js code I try to fetch data using graphql like so:
query($slug: String!) {
profile: markdownRemark(fields: { slug: { eq: $slug } }) {
html
frontmatter {
date(formatString: "DD MMMM, YYYY")
author
expertise
name
dev_type
degree
technologies
}
}
}
I expected the output to match a form like this:
technologies = ["tech1", "tech2", "tech3"]
but I get the following error(s) when trying to compile and run the site in development environment:
Case 1.
Errors:
Int cannot represent non-integer value: "tech1"
I don't know how to fix this problem. In my understanding this should work. Already tried to create new NodeField called technologies in gatsby-node.js but this did not work, too.
I also thought about another thing according this frontmatter, in my understanding frontmatter is a YAML, or rather the desired list of technologies in the markdown file is a YAML list, isn't it?
In this case I should adjust/extend the gatsby-transformer-yaml plugin in gatsby-config.js but I have no clue how to achieve this one the right/correct way.
Case 2.
Unfortunately I cannot reproduce my query anymore but I found a way where I didn't get the type error, but then technologies only contained null values (having same frontmatter content).
technologies = [ null, null, null, ...]
Found out technologies: [ tech1, tech2, tech3 ] is the solution for this problem. Actual Problem with this was that there was inconsistent data provided in my markdown files.
I am trying to pull all the documents in the collection 'users', but it only pulls 'fred' and 'lisa', and ignores all the italicized documents:
For this data:
Trying to get all documents:
Will yield:
info: length 2
info: fred => { gender: 'male', contacts: [ '' ] }
lisa => { contacts: [ '' ] }
According to the Firebase documentation (Firebase: Add and Manage Data):
Warning: Even though non-existent ancestor documents appear in the console, they do not appear in queries and snapshots. You must create the document to include it in query results.
Note: The non-existent ancestor users seem to be auto-created when the user hits the sign-up button that triggers a firebase.auth() function (fred and lisa were created manually).
How would I print the contacts of each user if some of the users do not show up in my queries? Would I need to periodically run a script that manually re-adds all the users or is there a more elegant solution?
As you have mentioned, these "documents" are displayed with an italic font in the Firebase console: this is because these documents are only present (in the console) as "container" of one or more sub-collection but they are not "genuine" documents.
As matter of fact, if you create a document directly under a col1 collection with the full path doc1/subCol1/subDoc1, no intermediate documents will be created (i.e. no doc1 document).
The Firebase console shows this kind of "container" (or "placeholder") in italics in order to "materialize" the hierarchy and allow you to navigate to the subDoc1 document but doc1 document doesn't exist in the Firestore database.
Let's take an example: Imagine a doc1 document under the col1 collection
col1/doc1/
and another one subDoc1 under the subCol1 (sub-)collection
col1/doc1/subCol1/subDoc1
Actually, from a technical perspective, they are not at all relating to each other. They just share a part of their path but nothing else. One side effect of this is that if you delete a document, its sub-collection(s) still exist.
So, if you want to be able to query for these parent documents, you will have to create them yourself, as jackz314 mentioned in the comments.
If you're trying to list all your registered users from Firebase auth, you can use the Firebase SDK function:
function listAllUsers(nextPageToken) {
admin.auth().listUsers(1000, nextPageToken)
.then(function(listUsersResult){
listUsersResult.users.forEach(function(userRecord) {
console.log('user', userRecord.toJSON());
})
if (listUsersResult.pageToken) {
// list next batch of users
}
})
.catch(function(err) {
console.log('Error listing users: ', error)
});
}
listAllUsers();
via http://firebase.google.com/docs/auth/admin/manage-users#list_all_users
The document says the cache can be automatically updated in the following example:
{
post(id: '5') {
id
score
}
}
mutation {
upvotePost(id: '5') {
id
score
}
}
Would the automatic update work in the following case when the cached object is in a list ? Like this:
This is a query that fetches a list of reviews:
{
reviewList(model:12){
list{
reviewId
text
}
cursor
}
}
When I update one of the reviews from the list to the server, react apollo does not automatically update the cached review:
mutation{
updateReview(reviewId:'1',text:'new text'){
reviewId
text
}
}
Is it a must to use update properties of the mutation component to update the cache?
Assuming you're using apollo-cache-inmemory, unless you are adding an item to or removing an item from the list, it shouldn't be necessary to use update. However, it's important to keep in mind that the cached data is normalized, and that Apollo utilizes the __typename and id (or _id) field to generate the cache key for any queried object. From the docs:
The InMemoryCache normalizes your data before saving it to the store
by splitting the result into individual objects, creating a unique
identifier for each object, and storing those objects in a flattened
data structure. By default, InMemoryCache will attempt to use the
commonly found primary keys of id and _id for the unique identifier if
they exist along with __typename on an object.
If the id field is missing, Apollo won't know how to match up the data from your query and the data from your mutation:
If id and _id are not specified, or if __typename is not specified,
InMemoryCache will fall back to the path to the object in the query,
such as ROOT_QUERY.allPeople.0 for the first record returned on the
allPeople root query. That would make data for given type scoped for
allPeople query and other queries would have to fetch their own
separate objects.
Fortunately, there's a workaround. You can of course just rename reviewId to id on the server side. However, if you want the cache to just use the reviewId field, you can pass in a dataIdFromObject function to your InMemoryCache constructor:
import { InMemoryCache, defaultDataIdFromObject } from 'apollo-cache-inmemory'
const cache = new InMemoryCache({
dataIdFromObject: object => {
switch (object.__typename) {
case 'Review': return object.reviewId
default: return defaultDataIdFromObject(object)
}
}
})
As long as resulting value is unique, you can utilize any other field (or combination of fields) in a similar fashion.