Note: This announcement is related to today's batch of released
urql
packages. You can see the release process and the changelogs here: urql-graphql/urql#1340
We have an entirely new release batch that we've just worked on. This batch includes some breaking changes and major releases, so we'd like to take a moment to talk about what has changed. It will luckily be surprisingly unsurprising and builds on the work that's already been ongoing for a while. Lastly we'll include a note on our upcoming roadmap, which we'll be kicking off soon.
Table of Contents
- urql update: February 2021
@urql/exchange-graphcache
reaches 4.0.0- GraphQLError-aware caching for
@urql/exchange-graphcache
- A
schema
shortcut to change root names (@urql/exchange-graphcache
) - Breaking Change: Good bye
operationName
- Breaking Change: Good bye
pollInterval
- When is my data
stale
? - Miscellaneous Changes
- How does Graphcache work?
- A New Roadmap: The
urql
Framework 2021
Another major bump! We've added two new exciting features and while we're excited about them (particularly the first) the major bump here is simply a precaution. We know that the last thing you'd want to see is different behaviour in a minor bump, especially if it concerns how data is cached and delivered, so here you go — v4.0.0!
Graphcache will now not cache fields that have errored.
As a GraphQL API executes your query document it may encounter unexpected errors. It'll deliver you some data still if that's
possible, so you may still see data: { ... }
instead of data: null
. However, it will also send us a list of GraphQLError
s.
These contain information about the original error but they also include path
information. This is a path
through the
result's JSON payload to tell us which field has errored. The API may then decide to just set a particular field to null
and
let us deal with the error.
Previously Graphcache would see the null
value and would just assume that that's exactly what the API meant. However,
a better default behaviour we wanted to see was to make it look at GraphQLError
s' path information and realise that this
field shouldn't be cached as null
.
{
user {
id
name
avatar {
imageUrl
}
}
}
To look at an example, consider a case where we send the above query to our API. The API may error on avatar
as it maybe
can't resolve some data or unexpectedly errors on an internal request.
This means that the API may give us a result that looks like this:
{
data: {
user: {
id: '123',
name: 'Example',
avatar: null,
}
},
errors: [
{
message: 'Could not load avatar URL',
path: ['user', 'avatar']
}
]
}
Graphcache now sees the error that tells us that the null
value at data.user.avatar
is set to null
because of this error, and will decide not to cache this null
value.
Instead, in your bindings, you'll still get the result as you'd expect (so as-is) but if you decide to refetch
this piece of data or restart this query (for instance, by navigating away and back) Graphcache will refetch the
query from the API without assuming this field is null
"forever".
We really and truly believe that Schema Awareness is an amazing feature to deliver partial results safely and build UIs that load in gradually rather than slowly while waiting for data that could also be loaded in the background.
However, we also use the schema's information in case your "root types" don't have their default names, like Query
,
Mutation
, and Subscription
. To remedy this you may now pass manually written but incomplete schema
data to Graphcache.
const introspection = {
__schema: {
queryType: {
name: 'query_root',
},
mutationType: {
name: 'mutation_root',
},
subscriptionType: {
name: 'subscription_root',
},
},
};
cacheExchange({ schema: introspection });
It's been a while now, but in #1045 in October 2020
we deprecated the Operation.operationName
property and renamed it to Operation.kind
.
This change was quickly applied to all of our first-party packages and we aimed to give everyone
time to apply this to their custom exchanges as well by issuing a warning when operationName
was
accessed.
Now that sufficient time has passed, we're removing the property entirely. Only Operation.kind
will
be the field signifying the operation's "kind". If you're creating new operations you may also use
the makeOperation
utility instead oif spreading operations. This is the new recommended approach
and the utility has been added in the same PR.
When upgrading @urql/core
please ensure that your package manager didn't install any duplicates of it.
You may deduplicate it manually using npx yarn-deduplicate
(for Yarn) or npm dedupe
(for npm)
This affects @urql/core
, urql
, @urql/preact, and
@urql/vue. We noticed that not a lot of people were relying on
pollInterval`.
And this makes compelete sense. Implementing polling executions outside of @urql/core
in userland
is pretty simple. It also may be more correct for your particular use-case. So overall we decided
to remove this API without issuing a deprecation, since it's also easily replaced. For instance in
React we may write:
const [result, executeQuery] = useQuery(...);
useEffect(() => {
if (!result.fetching) {
const id = setTimeout(() => executeQuery({ requestPolicy: 'network-only' }), 5000);
return () => clearTimeout(id);
}
}, [result.fetching, executeQuery]);
This is a very exciting feature that improves consistency a lot. You may know the stale
flag
on results which is passed all the way through to all of our bindings.
The stale
flag tells us whether we can expect data to be updated in the future and whether
we're looking at out-of-date data, in other words, it tells us whether urql
is fetching
new data in the background. And it's used frequently! It's used when cache-and-network
fetches new data in the background and it's used when Graphcache gives us partial data
while fetching the rest.
But the stale
flag was always only set actively and never passively. The Client
wouldn't give us stale: true
when a new background fetch started independently of the
caches. For instance, a query may start on a different component and we wouldn't know
about it, or Graphcache may decide to background fetch because of a mutation and our
useQuery
hook wouldn't hear about it.
We've decided to fix this. The stale
flag is now consistently updated to be true
when
the Client
sees that an operation is being refetched! 🎉
- @urql/exchange-persisted-fetch is at v1.3.0
- A new
enforcePersistedQueries
option disables Automatic Persisted Queries and will assume that persisted queries are the only mode of making requests.
- A new
- @urql/introspection is at v0.2.0 (not quite v1)
- The
minifyIntrospectionQuery
now removes some additional unneeded fields and information, like__Field
- The
It's hard at times to find accurate information on how Graphcache works. And we wanted to bring more information into our documentation to fix this. Hence, we decided to rewrite the entire introduction to "Normalized Caching" in our documentation!
https://formidable.com/open-source/urql/docs/graphcache/normalized-caching/
This will be a new and improved starting point into Graphcache and it'll clarify:
- What normalized caching is and how it works
- How Graphcache normalizes GraphQL data and retrieves it from memory
- How keying, updates, and local resolvers work
- How to think about Graphcache's data
We're planning to make more changes to the Graphcache documentation soon to make it even more approachable and make it cover more ground!
We're officially kicking off work on a new roadmap for urql
in 2021. Thanks to our friends at
The Guild who're open to talk about the parts in it that relate to
GraphQL Code Generator!
Graphcache and urql can provide an amazing experience, and we see a kind of "opinion gradient" in them (and Apollo, and Relay) where urql is very unopinionated. Even more so than Apollo since it doesn't enforce its normalised cache. Relay is the most opinionated since it enforces a pattern and has requirements for the schema. Graphcache (with urql) is actually more opinionated than Apollo but less so than Relay. It doesn't enforce a specific schema structure but it does enforce being "server-data only" so no local state, no divergence of the client state from the "server state", etc. Generally this comes down to patterns: We can have a better onboarding experience, we can add more features to make the switch from a document cache to a normalised cache seamless, and we can be even more like Relay.
How do we get there?
If we want to have a great experience for developers and leverage what Relay has already done, we need to optionally enforce more. We may all know about good practices, like colocating data requirements i.e. fragments to structural components and to combine all of them into one query per page. Relay achieves this with the Relay Compiler and other patterns. This can be major driver for having a coherent framework for GraphQL users. So we'd like to figure out:
- Strong type generation for the Graphcache config
- Huge improvements for the
@populate
directive, which will be like "document cache"-updates (or automatic refetchQueries) for the normalised cache - Compiler-like patterns and functionalities so that patterns are enforced and we can have one guide, one happy path to onboard new users