Skip to content

Instantly share code, notes, and snippets.

@kitten
Last active February 10, 2021 18:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kitten/7e55e48628eb76a8d65ec8bac8669a1f to your computer and use it in GitHub Desktop.
Save kitten/7e55e48628eb76a8d65ec8bac8669a1f to your computer and use it in GitHub Desktop.
urql update: February 2021

urql update: February 2021

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/exchange-graphcache reaches 4.0.0

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!

GraphQLError-aware caching for @urql/exchange-graphcache

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 GraphQLErrors. 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 GraphQLErrors' 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".

A schema shortcut to change root names (@urql/exchange-graphcache)

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 });

Breaking Change: Good bye operationName

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)

Breaking Change: Good bye pollInterval

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]);

When is my data stale?

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! 🎉

Miscellaneous Changes

  • @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.
  • @urql/introspection is at v0.2.0 (not quite v1)
    • The minifyIntrospectionQuery now removes some additional unneeded fields and information, like __Field

How does Graphcache work?

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!

A New Roadmap: The urql Framework 2021

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

We have a GitHub Project that tracks these goals. Let's go!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment