React query typescript example

Saved searches

Use saved searches to filter your results more quickly

You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session. You switched accounts on another tab or window. Reload to refresh your session.

React Query, Axios, Typescript example: get, post, put, delete — useQuery, useMutation, error handling

bezkoder/react-query-axios-typescript

This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?

Sign In Required

Please sign in to use Codespaces.

Launching GitHub Desktop

If nothing happens, download GitHub Desktop and try again.

Launching GitHub Desktop

If nothing happens, download GitHub Desktop and try again.

Launching Xcode

If nothing happens, download Xcode and try again.

Launching Visual Studio Code

Your codespace will open once ready.

There was a problem preparing your codespace, please try again.

Latest commit

Git stats

Files

Failed to load latest commit information.

README.md

React Query with Axios and Typescript example

React Client with React Query and Axios (Typescript) to make CRUD requests to Rest API in that:

  • React Query Axios Typescript GET request: get all Tutorials, get Tutorial by Id, find Tutorial by title
  • React Query Axios Typescript POST request: create new Tutorial
  • React Query Axios Typescript PUT request: update an existing Tutorial
  • React Query Axios Typescript DELETE request: delete a Tutorial, delete all Tutorials

react-query-axios-typescript

For instruction, please visit:

Fullstack with Node Express:

Fullstack with Spring Boot:

Integration (run back-end & front-end on same server/port)

This project was bootstrapped with Create React App.

About

React Query, Axios, Typescript example: get, post, put, delete — useQuery, useMutation, error handling

Источник

React Query and TypeScript

react query typescript

TypeScript is 🔥 — this seems to be a common understanding now in the frontend community. Many developers expect libraries to either be written in TypeScript, or at least provide good type definitions. For me, if a library is written in TypeScript, the type definitions are the best documentation there is. It’s never wrong because it directly reflects the implementation. I frequently look at type definitions before I read API docs.

React Query was initially written in JavaScript (v1), and was then re-written to TypeScript with v2. This means that right now, there is very good support for TypeScript consumers.

There are however a couple of «gotchas» when working with TypeScript due to how dynamic and unopinionated React Query is. Let’s go through them one by one to make your experience with it even better.

Generics

React Query heavily uses Generics. This is necessary because the library does not actually fetch data for you, and it cannot know what type the data will have that your api returns.

The TypeScript section in the official docs is not very extensive, and it tells us to explicitly specify the Generics that useQuery expects when calling it:

1function useGroups()
2 return useQueryGroup[], Error>( queryKey: ['groups'], queryFn: fetchGroups >)
3 >

Update: The docs have been updated and do not primarily encourage this pattern anymore.

Over time, React Query has added more Generics to the useQuery hook (there are now four of them), mainly because more functionality was added. The above code works, and it will make sure that the data property of our custom hook is correctly typed to Group[] | undefined as well as that our error will be of type Error | undefined . But it will not work like that for more advanced use-cases, especially when the other two Generics are needed.

The four Generics

This is the current definition of the useQuery hook:

1export function useQuery
2 TQueryFnData = unknown,
3 TError = unknown,
4 TData = TQueryFnData,
5 TQueryKey extends QueryKey = QueryKey
6 >

There’s a lot of stuff going on, so let’s try to break it down:

  • TQueryFnData : the type returned from the queryFn . In the above example, it’s Group[] .
  • TError : the type of Errors to expect from the queryFn . Error in the example.
  • TData : the type our data property will eventually have. Only relevant if you use the select option, because then the data property can be different from what the queryFn returns. Otherwise, it will default to whatever the queryFn returns.
  • TQueryKey : the type of our queryKey , only relevant if you use the queryKey that is passed to your queryFn .

As you can also see, all those Generics have default values, which means that if you don’t provide them, TypeScript will fall back to those types. This works pretty much the same as default parameters in JavaScript:

1function multiply(a, b = 2)
2 return a * b
3 >
4
5 multiply(10) // ✅ 20
6 multiply(10, 3) // ✅ 30

Type Inference

TypeScript works best if you let it infer (or figure out) what type something should be on its own. Not only does it make code easier to write (because you don’t have to type all the types 😅), but it will also make it easier to read. In many instances, it can make code look exactly like JavaScript. Some simple examples of type inference would be:

1const num = Math.random() + 5 // ✅ `number`
2
3 // 🚀 both greeting and the result of greet will be string
4 function greet(greeting = 'ciao')
5 return `$greeting>, $getName()>`
6 >

When it comes to Generics, they can also generally be inferred from their usage, which is super awesome. You could also provide them manually, but in many cases, you don’t need to.

1function identityT>(value: T): T
2 return value
3 >
4
5 // 🚨 no need to provide the generic
6 let result = identitynumber>(23)
7
8 // ⚠️ or to annotate the result
9 let result: number = identity(23)
10
11 // 😎 infers correctly to `string`
12 let result = identity('react-query')

Partial Type Argument Inference

. doesn’t exist in TypeScript yet (see this open issue). This basically means that if you provide one Generic, you have to provide all of them. But because React Query has default values for Generics, we might not notice right away that they will be taken. The resulting error messages can be quite cryptic. Let’s look at an example where this actually backfires:

1function useGroupCount()
2 return useQueryGroup[], Error>(
3 queryKey: ['groups'],
4 queryFn: fetchGroups,
5 select: (groups) => groups.length,
6 // 🚨 Type '(groups: Group[]) => number' is not assignable to type '(data: Group[]) => Group[]'.
7 // Type 'number' is not assignable to type 'Group[]'.ts(2322)
8 >)
9 >

Because we haven’t provided the 3rd Generic, the default value kicks in, which is also Group[] , but we return number from our select function. One fix is to simply add the 3rd Generic:

1function useGroupCount()
2 // ✅ fixed it
3 return useQueryGroup[], Error, number>(
4 queryKey: ['groups'],
5 queryFn: fetchGroups,
6 select: (groups) => groups.length,
7 >)
8 >

As long as we don’t have Partial Type Argument Inference, we have to work with what we got.

Infer all the things

Let’s start by not passing in any Generics at all and let TypeScript figure out what to do. For this to work, we need the queryFn to have a good return type. Of course, if you inline that function without an explicit return type, you will have any — because that’s what axios or fetch give you:

1function useGroups()
2 // 🚨 data will be `any` here
3 return useQuery(
4 queryKey: ['groups'],
5 queryFn: () => axios.get('groups').then((response) => response.data),
6 >)
7 >

If you (like me) like to keep your api layer separated from your queries, you’ll need to add type definitions anyways to avoid implicit any, so React Query can infer the rest:

1function fetchGroups(): PromiseGroup[]>
2 return axios.get('groups').then((response) => response.data)
3 >
4
5 // ✅ data will be `Group[] | undefined` here
6 function useGroups()
7 return useQuery( queryKey: ['groups'], queryFn: fetchGroups >)
8 >
9
10 // ✅ data will be `number | undefined` here
11 function useGroupCount()
12 return useQuery(
13 queryKey: ['groups'],
14 queryFn: fetchGroups,
15 select: (groups) => groups.length,
16 >)
17 >

Advantages of this approach are:

  • no more manually specifying Generics
  • works for cases where the 3rd (select) and 4th (QueryKey) Generic are needed
  • will continue to work if more Generics are added
  • code is less confusing / looks more like JavaScript

What about error?

What about error, you might ask? Per default, without any Generics, error will be inferred to unknown . This might sound like a bug, why is it not Error ? But it is actually on purpose, because in JavaScript, you can throw anything — it doesn’t have to be of type Error :

1throw 5
2 throw undefined
3 throw Symbol('foo')

Since React Query is not in charge of the function that returns the Promise, it also can’t know what type of errors it might produce. So unknown is correct. Once TypeScript allows skipping some generics when calling a function with multiple generics (see this issue for more information), we could handle this better, but for now, if we need to work with errors and don’t want to resort to passing Generics, we can narrow the type with an instanceof check:

1const groups = useGroups()
2
3 if (groups.error)
4 // 🚨 this doesn't work because: Object is of type 'unknown'.ts(2571)
5 return div>An error occurred: groups.error.message>/div>
6 >
7
8 // ✅ the instanceOf check narrows to type `Error`
9 if (groups.error instanceof Error)
10 return div>An error occurred: groups.error.message>/div>
11 >

Since we need to make some kind of check anyways to see if we have an error, the instanceof check doesn’t look like a bad idea at all, and it will also make sure that our error actually has a property message at runtime. This is also in line with what TypeScript has planned for the 4.4 release, where they’ll introduce a new compiler flag useUnknownInCatchVariables , where catch variables will be unknown instead of any (see here).

Type Narrowing

I rarely use destructuring when working with React Query. First of all, names like data and error are quite universal (purposefully so), so you’ll likely rename them anyway. Keeping the whole object will keep the context of what data it is or where the error is coming from. It will further help TypeScript to narrow types when using the status field or one of the status booleans, which it cannot do if you use destructuring:

1const data, isSuccess > = useGroups()
2 if (isSuccess)
3 // 🚨 data will still be `Group[] | undefined` here
4 >
5
6 const groupsQuery = useGroups()
7 if (groupsQuery.isSuccess)
8 // ✅ groupsQuery.data will now be `Group[]`
9 >

This has nothing to do with React Query, it is just how TypeScript works. @danvdk has a good explanation for this behaviour

Источник

Читайте также:  Jquery html выбранного элемента
Оцените статью