I'm brand new to Apollo and graphql, and I'm trying to setup an e-commerce site with shopify's storefront API. The website is build with react and Next.js for SSR.
I've managed to get some boilerplate code working for basic cart interactions with shopify. I have a Page component that wraps the whole app, and sits below ApolloProvider with access to apollo client. At the moment I'm using compose() to feed my Page component with some graphql (taken from this example):
const pageWithData = compose(
graphql(query), // Query that retrieves base shopify information, such as shop name, description and products
graphql(createCheckout, {name: "createCheckout"}), // Mutation that creates a new checkout object with shopify. Basically a cart object
graphql(checkoutLineItemsAdd, {name: "checkoutLineItemsAdd"}), // Mutation that adds a new lineitem to the checkout object
graphql(checkoutLineItemsUpdate, {name: "checkoutLineItemsUpdate"}), // Mutation that updates a line item
graphql(checkoutLineItemsRemove, {name: "checkoutLineItemsRemove"}), // Mutation that removes a lineitem
)(Page);
This all works as expected, Except when i refresh the browser, the cart is emptied, and a new checkout object is created. So what I want to do is store the checkout ID in localStorage and check if there is an ID in localstorage, before creating a new checkout object. If there is one, i'll load that checkout instead. right now the checkout is created like so in the Page component:
componentWillMount() {
this.props.createCheckout({
variables: {
input: {}
}}).then((res) => {
this.setState({
checkout: res.data.checkoutCreate.checkout
});
});
}
Now, I've found a working graphql query to load an existing checkout based on an ID:
const checkoutFetchQuery = gql`
query checkoutFetch ($checkoutId: ID!) {
node(id: $checkoutId) {
... on Checkout {
webUrl
subtotalPrice
totalTax
totalPrice
lineItems (first:250) {
pageInfo {
hasNextPage
hasPreviousPage
}
edges {
node {
title
variant {
title
image {
src
}
price
}
quantity
}
}
}
}
}
}
`;
And so I thought I could simply add this to the compose method like this:
const pageWithData = compose(
graphql(query), // Query that retrieves base shopify information, such as shop name, description and products
graphql(checkoutFetchQuery, { name: "fetchCheckout"}), // Query that fetches checkout based on a checkoutID
graphql(createCheckout, {name: "createCheckout"}), // Mutation that creates a new checkout object with shopify. Basically a cart object
graphql(checkoutLineItemsAdd, {name: "checkoutLineItemsAdd"}), // Mutation that adds a new lineitem to the checkout object
graphql(checkoutLineItemsUpdate, {name: "checkoutLineItemsUpdate"}), // Mutation that updates a line item
graphql(checkoutLineItemsRemove, {name: "checkoutLineItemsRemove"}), // Mutation that removes a lineitem
)(Page);
But this results in the following error from the Apollo dev tools:
GraphQL Errors: Variable checkoutId of type ID! was provided invalid value
I'm certain that this is me not understanding some key concept of how compose() work in react-apollo. I understand that I need to feed the query with some variables, but for some reason this query seem to run immediately on load, where as I expected this would simply make the query available on the component. Some of the other graphql() statements expects variables as well, such as "checkoutLineItemsAdd", but this doesn't result in errors. Another thing I've noticed is that the mutations are added as functions to the component props, where as my query is added as an object.
I'm struggling to find any good documentation on this.
Are queries run immediately?
Are mutations waiting to be called from the component, allowing us to dynamically add variables?
Should I write my gql syntax differently for it to become a function on the component instead of an object?
How do we pass variables dynamically to queries, when attached to the compose HOC?
Howcome I get errors from this query, and not the mutations, that also expect variables before running?
Your query requires an input checkoutId which is of type ID.
But your query graphql(checkoutFetchQuery, { name: "fetchCheckout"}) is being fired without any input. You can add the input variables by doing so
graphql(checkoutFetchQuery, {
name: "fetchCheckout",
options: {
variables: {
checkoutId: localstorage.get('checkoutid')
}
}
})
The docs for options config is here
You can also skip a query from autofiring by adding a skip check under options like
options: {
skip: !localstorage.get('checkoutid')
}
Related
I am using apollo v3 with a create-react app. I fire a query with useQuery and expect results to be cached, but they are not.
In my App.jsx, I have:
const client = new ApolloClient({
uri: `${api}/graphql`,
cache: new InMemoryCache()
})
I wrap my app with the ApolloProvider.
I have a provider that uses this graphql query to fetch a list of users:
const USERS_QUERY = gql`
query GetUsers {
users {
id
email
fullName
}
}
`
The query works, when I inspect the apollo tab in chrome devtools, I see nothing in the cache section.
My questions:
Why are the results not cached if I am using the useQuery from #apollo/client?
const { loading, error, data } = useQuery(USERS_QUERY)
I thought results should be cached automatically.
I also tried to add a type policy:
cache: new InMemoryCache({
typePolicies: {
Users: {
keyFields: ['id']
}
}
})
But I think I use this if I want to normalise with a different key the cache, or if I want to decide how to merge new data by myself. But first I need to have the data in my cache
As far as I know, this is because your query does not have any input arguments / variables, so the cache does not know what item to get from the cache when the query is called again. From what I understand, the cache is only used when a specific piece of data is looked for with an ID; otherwise, if it is a generic query, the data might have changed and so is not cached.
I've been following this Gatsby tutorial to create pages dynamically based on my CMS's creation of markdown files: https://www.gatsbyjs.org/docs/adding-markdown-pages/
I don't understand how the GraphQL query in the file blogTemplate.js receives the $path variable. I can see that the createPages function in gatsby-node.js creates the page based off the result of its own GraphQL query, and uses the 'path' frontmatter element to choose the URL for the created page.
However, how does that created page know its own path? The query called pageQuery in the tutorial uses the $path variable to source its own data, but I can't see how it receives that variable. Thank you in advance for any explanation.
While creating the pages, we can pass context,
All context values are made available to a template’s GraphQL queries as arguments prefaced with $
exports.createPages = async function ({ actions, graphql }) {
const { data } = await graphql(`
query {
allMarkdownRemark {
edges {
node {
fields {
slug
}
}
}
}
}
`)
data.allMarkdownRemark.edges.forEach(edge => {
const slug = edge.node.fields.slug
actions.createPage({
path: slug,
component: require.resolve(`./src/templates/blog-post.js`),
context: { path: slug }, //here you can pass path through context parameter, the slug can be then accesed under $path variable in the template
})
})
}
`
using $ sign we can then access the path value in template side
export const query = graphql`
query($path: String!) {
...
}
`
Graphql uses redux internall and all information regarding pages creation and paths are updated to gatsby's redux store and from thereon the graphql query is executed for each page
Now according to gatsby createPage github code comment
Data in "context" is passed to GraphQL as potential arguments when
running the page query.
When arguments for GraphQL are constructed, the context object is
combined with the page object so both page object and context data
are available as arguments. So you don't need to add the page "path"
to the context as it's already available in GraphQL. If a context
field duplicates a field already used by the page object, this can
break functionality within Gatsby so must be avoided.
I've just started using Gatsby with the Sanity headless CMS.
For the most part it's pretty straight forward; but knowing best practises for querying the data through GraphQL is still bothering me. How I'm doing it currently is just frantically clicking through my CMS structure in the GraphQL playground and finding what I want. This works but the lack of uniformity in this approach is making me uneasy.
For example, if I want a hero image that's in the CMS somewhere, i'll need to do something like:
query SomePageQuery($id: String) {
sanitySomePage(id: { eq: $id }) {
id
heroImage {
asset {
fluid(maxWidth: 1500) {
...GatsbySanityImageFluid
}
}
}
}
}
But if I want some PortableText block then I need to query the corresponding _raw field of whatever type. So, if my type was introText, Gatsby also provides a _rawIntroText. I'm only able to get the full PortableText from this _raw version of the data. Like this:
query SomePageQuery($id: String) {
sanitySomePage(id: { eq: $id }) {
id
_rawIntroText
}
}
It seems that, for some data you can use [Type], and sometimes you have to use _raw[Type].
There's not a great deal of documentation as to why this is the case. And I'm not sure if this is enforced via Sanity or Gatsby.
My question I guess would be, why does _raw[Anything] exist in the Gatsby and/or Sanity world, and how do people decide on which to use (other than just trial and error within the GraphQL playground and at runtime)?
This is coming from the gatsby-source-sanity plugin that Sanity built and maintains. Hopefully someone from Sanity can provide more context, but effectively the _raw[FieldName] entries return the original JSON data for the field. The unprefixed field (e.g. fieldName) is probably not what you want—it'll only contain bits of metadata about the data.
I tend to pull the _raw[FieldName] data and then just pass it straight into the #sanity/block-content-to-react component like so:
import React from "react"
import { graphql } from "gatsby"
import SanityBlockContent from "#sanity/block-content-to-react"
export default ({ data: { page } }) => (
<SanityBlockContent
blocks={page.textContent}
projectId={process.env.GATSBY_SANITY_PROJECT_ID}
dataset={process.env.GATSBY_SANITY_DATASET}
/>
)
export const query = graphql`
query SomePageQuery($id: String) {
page: sanitySomePage(id: { eq: $id }) {
textContent: _rawTextContent
}
}
`
Note that I'm using GraphQL aliasing to continue to refer to the field as textContent in my component rather than coupling the component to the specifics of this GraphQL schema.
You don't need to use Gatsby Image for Sanity images since they have their own image transformation pipeline anyways. Instead you can just fetch asset { _id } and then use #sanity/client like this to generate an image url:
import sanityClient from "#sanity/client"
import sanityImageUrl from "#sanity/image-url"
const client = sanityClient({
dataset: process.env.GATSBY_SANITY_DATASET,
projectId: process.env.GATSBY_SANITY_PROJECT_ID,
useCdn: true,
})
const builder = sanityImageUrl(client)
builder.image({ _id: "..." }).width(400).dpr(2).url()
I am trying to update my chache after succesfully executing a mutation. Here is my query and mutation:
export const Dojo_QUERY = gql`
query Dojo($id: Int!){
dojo(id: $id){
id,
name,
logoUrl,
location {
id,
city,
country
},
members{
id
},
disziplines{
id,
name
}
}
}`;
export const addDiszipline_MUTATION = gql`
mutation createDisziplin($input:DisziplineInput!,$dojoId:Int!){
createDisziplin(input:$input,dojoId:$dojoId){
disziplin{
name,
id
}
}
}`;
and my mutation call:
const [createDisziplin] = useMutation(Constants.addDiszipline_MUTATION,
{
update(cache, { data: { createDisziplin } }) {
console.log(cache)
const { disziplines } = cache.readQuery({ query: Constants.Dojo_QUERY,variables: {id}});
console.log(disziplines)
cache.writeQuery({
...some update logic (craches in line above)
});
}
}
);
when i execute this mutation i get the error
Invariant Violation: "Can't find field dojo({"id":1}) on object {
"dojo({\"id\":\"1\"})": {
"type": "id",
"generated": false,
"id": "DojoType:1",
"typename": "DojoType"
}
}."
In my client cache i can see
data{data{DojoType {...WITH ALL DATA INSIDE APPART FROM THE NEW DISZIPLINE}}
and
data{data{DisziplineType {THE NEW OBJECT}}
There seems to be a lot of confusion around the client cache around the web. Somehow none of the posed solutions helped, or made any sense to me. Any help would be greatly appreciated.
EDIT 1:
Maybe this can help?
ROOT_QUERY: {…}
"dojo({\"id\":\"1\"})": {…}
generated: false
id: "DojoType:1"
type: "id"
typename: "DojoType"
<prototype>: Object { … }
<prototype>: Object { … }
Edit 2
I have taken Herku advice and started using fragment. however it still seems to not quite work.
My udated code:
const [createDisziplin] = useMutation(Constants.addDiszipline_MUTATION,
{
update(cache, { data: { createDisziplin } }) {
console.log(cache)
const { dojo } = cache.readFragment(
{ fragment: Constants.Diszilines_FRAGMENT,
id:"DojoType:"+id.toString()});
console.log(dojo)
}
}
);
with
export const Diszilines_FRAGMENT=gql`
fragment currentDojo on Dojo{
id,
name,
disziplines{
id,
name
}
}
`;
however the result from console.log(dojo) is still undefined.Any advice?
So I think your actual error is that you have to supply the ID as as a string: variables: {id: id.toString()}. You can see that these two lines are different:
dojo({\"id\":1})
dojo({\"id\":\"1\"})
But I would highly suggest to use readFragment instead of readQuery and update the dojo with the ID supplied. This should update the query as well and all other occurrences of the dojo in all your queries. You can find documentation on readFragment here.
Another trick is as well to simply return the whole dojo in the response of the mutation. I would say people should be less afraid of that and not do to much cache updates because cache updates are implicit behaviour of your API that is nowhere in your type system. That the new disziplin can be found in the disziplins field is now encoded in your frontend. Imagine you want to add another step here where new disziplins have to be approved first before they end up in there. If the mutation returns the whole dojo a simple backend change would do the job and your clients don't have to be aware of that behaviour.
I just started using apollo client on a React application and I'm stuck on caching.
I have a home page with a list of products where I do a query to get the id and name of those products and a product page where I do query for the ID, name, description and image.
I would like that if a user visits the home page fist then a specific product page to only do a query for that product's description and image, also display the name during the loading (since I should have cached it already).
I followed "Controlling the Store" part of the documentation (http://dev.apollodata.com/react/cache-updates.html) but still couldn't resolve it.
The query that is done when we go to the product page still asks for both the product's id and name whereas they should be cached since I already asked for them.
I think I'm missing something but I can't figure it out.
Here is a bit of the code:
// Create the apollo graphql client.
const apolloClient = new ApolloClient({
networkInterface: createNetworkInterface({
uri: `${process.env.GRAPHQL_ENDPOINT}`
}),
queryTransformer: addTypename,
dataIdFromObject: (result) => {
if (result.id && result.__typename) {
console.log(result.id, result.__typename); //can see this on console, seems okey
return result.__typename + result.id;
}
// Make sure to return null if this object doesn't have an ID
return null;
},
});
// home page query
// return an array of objects (Product)
export default graphql(gql`
query ProductsQuery {
products {
id, name
}
}
`)(Home);
//product page query
//return an object (Product)
export default graphql(gql`
query ProductQuery($productId: ID!) {
product(id: $productId) {
id, name, description, image
}
}
`,{
options: props => ({ variables: { productId: props.params.id } }),
props: ({ data: { loading, product } }) => ({
loading,
product,})
})(Product);
And my console output:
The answer to your question actually has two parts:
The client cannot actually tell for sure that these queries resolve to the same object in the cache, because they have a different path. One starts with products, the other with product. There's an open PR for client-side resolvers, which will let you give the client hints about where to find things in the cache, even if you haven't explicitly queried for them. I expect that we will publish that feature within a week or two.
Even with client-side resolvers, Apollo Client won't do exactly what you described above, because Apollo Client no longer does query diffing since version 0.5. Instead, all queries are fully static now. That means even if your query is in the cache partially, the full query will be sent to the server. This has a number of advantages that are laid out in this blog post.
You will still be able to display the part that's in the cache first, by setting returnPartialData: true in the options.
This question is quite old, however, there is a solution to map the query to the correct location using cacheRedirects
In my project, I have a projects query and a project query.
I can make a cacheRedirect like below:
const client = new ApolloClient({
uri: "http://localhost:3000/graphql",
request: async (operation) => {
const token = await localStorage.getItem('authToken');
operation.setContext({
headers: {
authorization: token
}
});
},
cacheRedirects: {
Query: {
project: (_, { id }, { getCacheKey }) => getCacheKey({ id, __typename: 'Project' })
}
}
});
Then when I load my dashboard, there is 1 query which gets projects. And then when navigating to a single project. No network request is made because it's reading from the cache 🎉
Read the full documentation on Cache Redirects