I am trying to use Apollo Cache specifically for local state management. I am using Apollo Client 3. I am trying to write default state ( a simple object ) using writeQuery in the cache, but it doesn't get written in the cache. I can't find what I am missing here.
const DEFAULT_STATE_QUERY = gql`
query defaultState{
id
name
age
}
`;
and
client.writeQuery({
query: DEFAULT_STATE_QUERY,
data: {
defaultState: {
__typename: "DefaultState",
id: 1,
name: "Biryani",
age: 45
},
},
});
I checked that we don't need typeDefs for reading and writing to cache as long as we don't want introspection to work out in apollo dev-tools for example.
Hence I initialised a simple client
const client = new ApolloClient({
cache: new InMemoryCache()
});
and provided it to my components using ApolloProvider
Inspite of running the code which does client.writeQuery, I dont see anything in Apollo cache and expectedly, one of the components running a useQuery too returns undefined data. What am I missing ?
Related
When I use relayStylePagination helper funtion in Apollo Client cache type policies, changing the variables passed to the query doesn't call the query again even if I call the refetch function manually.
It perfectly does the fetchMore behavior and merges the new data.
My cache field looks like this:
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
inventories: relayStylePagination(),
},
},
},
})
I needed to manually call refetch while passing the new variables.
Still have no clue why adding relay type policy prevents triggering refetch automatically.
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 am using GraphQL with Apollo-Client in my React(Typescript) application with an in memory cache. The cache is updated on new items being added which works fine with no errors.
When items are removed a string is returned from GraphQL Apollo-Server backend stating the successful delete operation which initiates the update function to be called which reads the cache and then modifies it by filtering out the id of the item. This is performed using the mutation hook from Apollo-Client.
const [deleteBook] = useMutation<{ deleteBook: string }, DeleteBookProps>(DELETE_BOOK_MUTATION, {
variables: { id },
onError(error) {
console.log(error);
},
update(proxy) {
const bookCache = proxy.readQuery<{ getBooks: IBook[] }>({ query: GET_BOOKS_QUERY });
if (bookCache) {
proxy.writeQuery<IGetBooks>({
query: GET_BOOKS_QUERY,
data: { getBooks: bookCache.getBooks.filter((b) => b._id !== id) },
});
}
},
});
The function works and the frontend is updated with the correct items in cache, however the following error is displayed in the console:
Cache data may be lost when replacing the getBooks field of a Query object.
To address this problem (which is not a bug in Apollo Client), define a custom merge function for the Query.getBooks field, so InMemoryCache can safely merge these objects:
existing: [{"__ref":"Book:5f21280332de1d304485ae80"},{"__ref":"Book:5f212a1332de1d304485ae81"},{"__ref":"Book:5f212a6732de1d304485ae82"},{"__ref":"Book:5f212a9232de1d304485ae83"},{"__ref":"Book:5f21364832de1d304485ae84"},{"__ref":"Book:5f214e1932de1d304485ae85"},{"__ref":"Book:5f21595a32de1d304485ae88"},{"__ref":"Book:5f2166601f6a633ae482bae4"}]
incoming: [{"__ref":"Book:5f212a1332de1d304485ae81"},{"__ref":"Book:5f212a6732de1d304485ae82"},{"__ref":"Book:5f212a9232de1d304485ae83"},{"__ref":"Book:5f21364832de1d304485ae84"},{"__ref":"Book:5f214e1932de1d304485ae85"},{"__ref":"Book:5f21595a32de1d304485ae88"},{"__ref":"Book:5f2166601f6a633ae482bae4"}]
For more information about these options, please refer to the documentation:
* Ensuring entity objects have IDs: https://go.apollo.dev/c/generating-unique-identifiers
* Defining custom merge functions: https://go.apollo.dev/c/merging-non-normalized-objects
Is there a better way to update the cache so this error won't be received?
I too faced the exact same warning, and unfortunately didn't come up with a solution other than the one suggested here: https://go.apollo.dev/c/merging-non-normalized-objects
const client = new ApolloClient({
....
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
getBooks: {
merge(existing, incoming) {
return incoming;
},
},
},
},
}
}),
});
(I am not sure weather I wrote your fields and types correctly though, so you might change this code a bit)
Basically, the code above let's apollo client how to deal with mergeable data. In this case, I simply replace the old data with a new one.
I wonder though, if there's a better solution
I've also faced the same problem. I've come across a GitHub thread that offers two alternative solutions here.
The first is evicting what's in your cache before calling cache.writeQuery:
cache.evict({
// Often cache.evict will take an options.id property, but that's not necessary
// when evicting from the ROOT_QUERY object, as we're doing here.
fieldName: "notifications",
// No need to trigger a broadcast here, since writeQuery will take care of that.
broadcast: false,
});
In short this flushes your cache so your new data will be the new source of truth. There is no concern about losing your old data.
An alternative suggestion for the apollo-client v3 is posted further below in the same thread:
cache.modify({
fields: {
notifications(list, { readField }) {
return list.filter((n) => readField('id', n) !==id)
},
},
})
This way removes a lot of boilerplate so you don't need to use readQuery, evict, and writeQuery. The problem is that if you're running Typescript you'll run into some implementation issues. Under-the-hood the format used is InMemoryCache format instead of the usual GraphQL data. You'll be seeing Reference objects, types that aren't inferred, and other weird things.
I have deployed the apollo-server on Heroku.
Url https://rocky-sands-20819.herokuapp.com/
A testing query
query Users {
allUsers {
name
}
}
You can see you succesfully get response.
But when I try use it in react app, the query will return an error.
Here is the restlink
const restLink = new RestLink({
uri: "https://rocky-sands-20819.herokuapp.com/",
});
export const client = new ApolloClient({
link: restLink,
cache: new InMemoryCache(),
});
When I use const { data, loading, error } = useQuery(GET_USERS) The error will return an error saying Error: Network error: forward is not a function - I am using the same query as in playground
This react-app works, when I test with https://jsonplaceholder.typicode.com/
What goes wrong?
SOLUTION:
Works after I replaced
const restLink = new RestLink({
uri: "https://rocky-sands-20819.herokuapp.com/",
});
with
const httpLink = createHttpLink({
uri: "https://rocky-sands-20819.herokuapp.com/graphql"
});
I think, if you sending GraphQL queries to a REST endpoint (that's what I assume from RestLink usage) you may need to change slightly your queries.
You may find the examples here; https://www.apollographql.com/docs/link/links/rest/#quick-start
#rest directive is used within the queries to specify the end point. Like following;
const query = gql`
query luke {
person #rest(type: "Person", path: "people/1/") {
name
}
}`;
I don't know how your backend is designed and I have never used GraphQL with REST backend but I hope it is helpful.
If your backend is not a REST service may be you should not use RestLink and simply specify the URI within the ApolloClient constructor.
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')
}