-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Return null from readQuery when data is not in store #1
Comments
Longer discussion of this on a closed task (it is still a problem) apollographql/apollo-client#1542 |
Thanks @bbugh. Updated title & description—it looks like returning null is the favored behavior. I'm for labeling |
The consensus does seem to be to return const query = gql`{ thing { name } }`
const data = { thing: { name: null } }
// Write a null value to the cache
store.writeQuery({ query, data })
// found query on readQuery, so it should return the value
store.readQuery(query)
=> null // this is *actually* null
// proposal for null: doesn't make sense to return null because the query doesn't exist
// no way to tell if this is actually a real value OR the query failed
store.readQuery(gql`{ missingQuery { id } }`)
=> null // this isn't null, it's missing
// proposal for undefined: query doesn't exist so it's not defined
store.readQuery(gql`{ missingQuery { id } }`)
=> undefined // correct - it's not actually defined |
Despite this being quite an annoying bug that forces us into catching the error returned from import { Injectable } from '@angular/core';
import { DataProxy } from 'apollo-cache';
@Injectable({
providedIn: 'root'
})
export class GraphQLService {
public readQuery<T>(proxy: DataProxy, query: any): T | undefined {
try {
const data = proxy.readQuery<T>({ query });
return data;
} catch {
return undefined;
}
}
} You can consume this service anywhere and use it to "safely" use |
I have also been using a workaround (albeit a pretty ugly one). The code below has allowed me to call const cache = new InMemoryCache();
// TODO: Monkey-patching in a fix for an open issue suggesting that
// `readQuery` should return null or undefined if the query is not yet in the
// cache: https://github.com/apollographql/apollo-feature-requests/issues/1
cache.originalReadQuery = cache.readQuery;
cache.readQuery = (...args) => {
try {
return cache.originalReadQuery(...args);
} catch (err) {
return undefined;
}
};
return new ApolloClient({
link,
cache,
}); |
I'd like to contend that this is a bug and not a feature-request. The type definition of Further, if the official apollo docs suggest that the
...then we really should have expected, intuitive & tight behavior here. This bug opens up a whole new class of runtime errors that won't be found until production. These errors occur quite frequently as new features are rolled out in production (lots of empty collections throughout the app's state!). When a user creates a new resource in a collection, but has never "fetched that collection via an apollo query", when we try to This feels like a good candidate to be supported in v3.0. It will break the interface of those who rely on the exception to catch this error, which can be justified in a major release. |
@mcdougal thanks for that monkey-patching solution you suggested. Seems most prudent and the right place to catch the errors or invariant violations. +1 on having the Apollo team address this in v3.0. |
this is clearly a non expected behavior (and very difficult to discover, it happens only in very particular cases) I am not sure to understand in what circumstances readQuery returns null |
This is a bug and not a feature request. |
Agreed. Where is the right place to file this then? |
Actually this is documented behavior, so it's not a bug.
|
The reason this issue is here and still open is because many of us do consider this a bug, and are asking for Apollo to reconsider the current documented behavior. |
Type for readQuery already has null, so anyone using TypeScript should have backwards compatibility covered. readQuery<QueryType, TVariables = any>(
options: DataProxy.Query<TVariables>,
optimistic?: boolean,
): QueryType | null; Any update on this feature/bug? Currently we have this in code: let result: ListQueryResult | null = null;
try {
result = cache.readQuery<ListQueryResult>({ query: list });
} catch (e) {
// https://github.com/apollographql/apollo-feature-requests/issues/1
}
const newItem = data?.createItem?.item;
if (result !== null && newItem !== undefined) {
cache.writeQuery({
query: list,
data: { items: result.items.concat([newItem]) },
});
} Which is getting quite ridicules |
Writing data to a specific entity object should not require the additional boilerplate of creating a named fragment with a type condition. All the cache really cares about is the top-level selection set of the query or fragment used to read or write data, and from that perspective queries are an almost perfect substitute for fragments, in almost all cases. In other words, this code cache.writeFragment({ id, fragment: gql` fragment UnnecessaryFragmentName on UnnecessaryType { firstName lastName } `, data: { __typename: "UnnecessaryType", firstName, lastName, }, }); can now be written as just cache.writeQuery({ id, query: gql`{ firstName lastName }`, data: { firstName, lastName }, }); Once you get used to using queries instead of fragments, you may begin to wonder why you ever thought fragments were a good idea. To save you some existential turmoil: fragments are still a good idea if you really need their type condition behavior (that is, if you want the fragment to match only when the `on SomeType` condition holds), but otherwise passing options.id and options.query to cache.readQuery or cache.writeQuery is a lot simpler and just as powerful. If you need further convincing, the cache.readFragment and cache.writeFragment methods actually convert the given options.fragment document to a query document (using getFragmentQueryDocument) which includes the given options.fragment as a ...field. You can skip that needless conversion by just providing options.query to readQuery or writeQuery, and reserving readFragment and writeFragment for the rare cases where fragments actually have advantages over queries. We are not removing the cache.readFragment and cache.writeFragment methods at this time, though we may consider deprecating them in the future. As a side benefit, these changes happen to solve our longest outstanding feature request, apollographql/apollo-feature-requests#1, which recently inspired #5866. That's because options.rootId is now always defined when we do this check: https://github.com/apollographql/apollo-client/blob/9cd5e8f7552db65437b5e59b605268a336977e45/src/cache/inmemory/inMemoryCache.ts#L130-L134
Writing data to a specific entity object should not require the additional boilerplate of creating a named fragment with a type condition. All the cache really cares about is the top-level selection set of the query or fragment used to read or write data, and from that perspective queries are an almost perfect substitute for fragments, in almost all cases. In other words, this code cache.writeFragment({ id, fragment: gql` fragment UnnecessaryFragmentName on UnnecessaryType { firstName lastName } `, data: { __typename: "UnnecessaryType", firstName, lastName, }, }); can now be written as just cache.writeQuery({ id, query: gql`{ firstName lastName }`, data: { firstName, lastName }, }); Once you get used to using queries instead of fragments, you may begin to wonder why you ever thought fragments were a good idea. To save you some existential turmoil: fragments are still a good idea if you really need their type condition behavior (that is, if you want the fragment to match only when the `on SomeType` condition holds), but otherwise passing options.id and options.query to cache.readQuery or cache.writeQuery is a lot simpler and just as powerful. If you need further convincing, the cache.readFragment and cache.writeFragment methods actually convert the given options.fragment document to a query document (using getFragmentQueryDocument) which includes the given options.fragment as a ...field. You can skip that needless conversion by just providing options.query to readQuery or writeQuery, and reserving readFragment and writeFragment for the rare cases where fragments actually have advantages over queries. We are not removing the cache.readFragment and cache.writeFragment methods at this time, though we may consider deprecating them in the future. As a side benefit, these changes happen to solve our longest outstanding feature request, apollographql/apollo-feature-requests#1, which recently inspired #5866. That's because options.rootId is now always defined when we do this check: https://github.com/apollographql/apollo-client/blob/9cd5e8f7552db65437b5e59b605268a336977e45/src/cache/inmemory/inMemoryCache.ts#L130-L134
Any ETA on this one? |
@impowski Check out the commit message here: apollographql/apollo-client@6a4e460 Looks like it'll be included in v3 |
@dylanwulf oh, I’ve seen it but didn’t read the last part. Thanks. |
v3 is already out, but I'm getting errors when attempting to query non-existing fields. Is there any news on this issue? |
I'm just in the process of upgrading to v3 and this is one of the issues I'm concerned with the most. Any updates? |
If it helps, I was only using readQuery to then use writeQuery with modified data, but I learned the hard way that cache.modify is the better way to go. So I have just avoided this issue entirely :) |
Any updates on this? |
@JesseZomer + others: it's finally happening! apollographql/apollo-client#7098 Fine print: we would very much appreciate everyone's help validating these changes once they are released in a beta version of Apollo Client 3.3 (see #7015). If this turns out to be a major breaking change (🤞 ), it may have to wait for AC4, but we are hopeful that it will not be very disruptive, since it merely turns exceptions into Thanks to everyone (especially @MaxDesiatov and @lorensr, originally) for voicing your support for this change over the past 2+ years! Hopefully it sticks this time! |
How to reproduce the issue:
Intended outcome:
Returns null
Actual outcome:
Throws an error:
ERROR Error: Uncaught (in promise): Error: Can't find field notInTheStore on object (ROOT_QUERY) undefined.
Migrated from:
apollographql/apollo-client#2007
apollographql/apollo-client#1542
The text was updated successfully, but these errors were encountered: