{
"$type": "site.standard.document",
"canonicalUrl": "https://johnnyreilly.com/posts/strongly-typing-react-query-s-usequeries",
"description": "Learn how to strongly type `useQueries` in `react-query` with `useQueriesTyped`. A wrapper function provides the strongly-typed API.",
"path": "/posts/strongly-typing-react-query-s-usequeries",
"publishedAt": "2021-01-03T00:00:00.000Z",
"site": "at://did:plc:yy3apqjlms24kso7ahn7lbmb/site.standard.publication/3mova7c4nho2b",
"tags": [
"react"
],
"textContent": "react-query has a weakly typed hook named useQueries. It's possible to turn that into a strong typed hook; this post shows you how.\n\n\n\nUpdated April 2022\n\nYou don't need this blog post! Just use a react-query@3.28.0 or greater; artysidorenko contributed a PR that moved this behaviour into the package.\n\nWhat is useQueries?\n\nIf you haven't used react-query then I heartily recommend it. It provides (to quote the docs):\n\n> Hooks for fetching, caching and updating asynchronous data in React\n\nWith version 3 of react-query, a new hook was added: useQueries. This hook allows you fetch a variable number of queries at the same time. An example of what usage looks like is this (borrowed from the excellent docs):\n\nWhilst react-query is written in TypeScript, the way that useQueries is presently written strips the types that are supplied to it. Consider the signature of the useQueries:\n\nThis returns an array of UseQueryResult:\n\nAs you can see, no type parameters are passed to UseQueryResult in the useQueries signature and so it takes the default types of unknown. This forces the consumer to either assert the type that they believe to be there, or to use type narrowing to ensure the type. The former approach exposes a possibility of errors (the user can specify incorrect types) and the latter approach requires our code to perform type narrowing operations which are essentially unnecessary (the type hasn't changed since it was returned; it's simply been discarded).\n\nWhat if there was a way to strongly type useQueries so we neither risked specifying incorrect types, nor wasted precious lines of code and CPU cycles performing type narrowing? There is my friends, read on!\n\nuseQueriesTyped - a strongly typed wrapper for useQueries\n\nIt's possible to wrap the useQueries hook with our own useQueriesTyped hook which exposes a strongly typed API. It looks like this:\n\nLet's unpack this. The first and most significant thing to note here is that queries moves from being UseQueryOptions[] to being TQueries extends readonly UseQueryOptions[] \\- far more fancy! The reason for this change is we want the type parameters to flow through on an element by element basis in the supplied array. TypeScript 4's variadic tuple types should allow us to support this. So the new array signature looks like this:\n\nWhere TQueries is\n\nWhat this means is, that each element of the rest parameters array must have a type of readonly UseQueryOptions. Otherwise the compiler will shout at us (and rightly so).\n\nSo that's what's coming in.... What's going out? Well the return type of useQueriesTyped is the tremendously verbose:\n\nLet's walk this through. First of all we'll look at this bit:\n\nOn the face of it, it looks like we're returning an Object, not an Array. There's nuance here; JavaScript Arrays are Objects.\n\nMore specifically, by approaching the signature this way, we can acquire the ArrayElement type which represents each of the keys of the array. Consider this array:\n\nFor the above, ArrayElement would take the values 0, 1 and 2. And this is going to prove useful in a moment as we're going to index into our TQueries object to surface up the return types for each element of our return array from there.\n\nNow let's look at the return type for each element. The signature of that looks like this:\n\nGosh... Well there's a lot going on here. Let's start in the middle and work our way out.\n\nThe above code indexes into our TQueries array for each element of our strongly typed indexer ArrayElement. So it might resolve the first element of an array to { queryKey: 'key1', queryFn: () => 1 }, for example. Next:\n\nWe're now taking the type of each element provided, and grabbing the type of the queryFn property. It's this type which contains the type of the data that will be passed back, that we want to make use of. So for an examples of [{ queryKey: 'key1', queryFn: () => 1 }, { queryKey: 'key2', queryFn: () => 'two' }, { queryKey: 'key3', queryFn: () => new Date() }] we'd have the type: const result: [() => number, () => string, () => Date].\n\nThe next stage is using NonNullable on our queryFn, given that on UseQueryOptions it's an optional type. In our use case it is not optional / nullable and so we need to enforce that.\n\nNow we want to get the return type of our queryFn \\- as that's the data type we're interested. So we use TypeScript's ReturnType for that.\n\nHere we're using TypeScript 4.1's recursive conditional types to unwrap a Promise (or not) to the relevant type. This allows us to get the actual type we're interested in, as opposed to the Promise of that type. Finally we have the type we need! So we can do this:\n\nIt's at this point where we reach a conditional type in our type definition. Essentially, we have two different typing behaviours in play:\n\n1. Where we're inferring the return type of the query\n2. Where we're inferring the return type of a select. A select option can be used to transform or select a part of the data returned by the query function. It has the signature: select: (data: TData) => TSelect\n\nWe've been unpacking the first of these so far. Now we encounter the conditional type that chooses between them:\n\nWhat's happening here is:\n\n- if a query includes a select option, we infer what that is and then subsequently extract the return type of the select.\n- otherwise we use the query return type (as we we've previously examined)\n\nFinally, whichever type we end up with, we supply that type as a parameter to UseQueryResult. And that is what is going to surface up our types to our users.\n\nUsage\n\nSo what does using our useQueriesTyped hook look like?\n\nWell, supplying queryFns with different signatures looks like this:\n\nAs you can see, we're being returned a Tuple and the exact types are flowing through.\n\nNext let's look at a .map example with identical types in our supplied array:\n\nThe return type of number is flowing through for each element.\n\nFinally let's look at how .map handles arrays with different types of elements:\n\nAdmittedly this last example is a somewhat unlikely scenario. But again we can see the types flowing through - though further narrowing would be required here to get to the exact type.\n\nIn the box?\n\nIt's great that we can wrap useQueries to get a strongly typed experience. It would be tremendous if this functionality was available by default. There's a discussion going on around this. It's possible that this wrapper may no longer need to exist, and that would be amazing. In the meantime; enjoy!",
"title": "react-query: strongly typing useQueries"
}