Slicing Data with Tanstack Query
Tags: react, javascript, typescript, reactquery
Published: 2023-03-25 14:00:00

---
title: Slicing Data with Tanstack Query
published: true
date: 2023-03-25 14:00:00 UTC
canonical_url: https://www.simonporter.co.uk/posts/slicing-data-with-tanstack-query/
tags: react,javascript,typescript,reactquery
---

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o9rps7lero7vdavfjp7q.jpeg)

I've been working with [Tanstack Query](https://tanstack.com) for a while now and I'm a big, big, fan. I love how it works within my apps, nestled amongst my standard React code without needing a tonne of boilerplate, and how it takes care of the hard bits of caching for me.

Nobody wants to roll their own solutions to these hard problems[^2hardproblems], and Tanstack Query "_just works_" for the projects I've used it on.

Big props to [TkDodo](https://twitter.com/TkDodo) for his continued support!

## Slicing Data

Lately, I've been looking at slicing data with the `select` feature. TkDodo explains this on his blog: [React Query Data Transformations](https://tkdodo.eu/blog/react-query-data-transformations) but I thought I'd write up my learnings[^whyanotherblog] and talk about how to use them with TypeScript.

The select feature behaves like a redux selector. That is, it lets you take a larger piece of state, and slice it to extract just the parts you're interested in.

## Quick Example

[View the sample on Codesandbox!](https://codesandbox.io/s/quizzical-herschel-95rryw)

The above example uses the `useGetUser()` custom hook which calls `useQuery` to request `User` data from our backend.

```
    export function useGetUser<T = User>({
        userId,
        options
    }: {
        userId: number;
        options?: {
            select?: (user: User) => T;
        };
    }) {
    return useQuery({
        queryKey: ["user", { userId }],
        queryFn: async () => {
            return dataOne as User;
        },
        ...options
    });
    }
```

We've then created a custom `useGetGender()` hook which uses **select** to slice off just the `gender` value from this data.

```
    const selectGender = (user: User) => user?.gender;

    export function useGetGender({ userId }: { userId: number }) {
        return useGetUser({
            userId,
            options: {
                select: selectGender,
            },
        })
    }
```

This is really powerful!

You can re-use the same state and slice off the bits you want without needing to make extra requests to other endpoints for the exact same data. Preventing more request waterfalls[^gtmetrix] and potentially reducing rerenders[^rerenders].

## TypeScript Settings

You may have been expecting some _Generic Gymnastics_ in the code above, in order to type the return values and support all options with `UseQueryOptions<>`.

For the most part, the recommendation is to let TypeScript do it's thing and infer the values for you. I know, cheating right?

```
    return useQuery({
        queryKey: ["user", { userId }],
        queryFn: async () => {
            return dataOne as User;
        },
        ...options
    });
```

This is lying to TS to tell it we're returning a `User`, but unless you plan on adding [Zod](https://tkdodo.eu/blog/type-safe-react-query#zod), this is about the best you can do.

This then lets TS infer the return of our own custom hook, because `useQuery` infers what data it will return from the **queryFn** and so our custom hook infers the same return type.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tl7s3pmubth59hdiqci1.png)

This makes sense, but what about the **select** ReturnType? That doesn't return the same data, and we could have multiple select's returning different data couldn't we?

We could always return what we need for this particular select, but that won't work for others with other data types.

```
    export function useGetUser({
        userId,
        options
    }: {
        userId: number;
        options?: {
            select?: (user: User) => string // Help??;
        };
    }) {
```

Rather than start making unions for each data type you want returned, the solution is to use a generic on the custom hook to adjust the Return Type with a default.

This way if we use a **select** function in another custom hook, the _return type of the select_ is passed through.

Otherwise, if no select is used, it defaults to the data we expect (the `User`).

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nyxamipshyv6g9ohzqdr.png)

## Considerations

This isn't the only way to set these up though. You could go full hog and provide all the Generics, all the time, and type all the options (with `UseQueryOptions<>`) but... [it's not recommended](https://tkdodo.eu/blog/react-query-and-type-script#infer-all-the-things).

TkDodo has said that new Generics will be introduced in v5, and if you are currently only provide the existing ones, this may break on upgrade.

I'm happy with the trade offs from the above setup though, most inferrance with least duplication.

[^2hardproblems]: "_There are only two hard things in Computer Science: cache invalidation and naming things._" - [Phil Karlton](https://martinfowler.com/bliki/TwoHardThings.html)
[^whyanotherblog]: In my [initial blog post](/posts/yet-another-developer-blog), I write about why I wanted to write about my learnings. To cement my knowedge mostly, and if it's beneifical to others, that's a plus too!
[^gtmetrix]: Take a look at [the GTMetrix blog](https://gtmetrix.com/blog/how-to-read-a-waterfall-chart-for-beginners/) for an explanation of request waterfalls.
[^rerenders]: It's actually a little more involved than that, while it can help, you should be aware of [render optimizations in React Query](https://tkdodo.eu/blog/react-query-render-optimizations) first.