T
TanStackβ€’3y ago
deep-jade

How to use react query properly with zod and typescript, and also using the query factory style.

I want to use zod and typescript with react query. I want to implement something like this but with zod. https://twitter.com/ralex1993/status/1570036707134676994?t=PDiHDOvdhk6b_t_uAyoj4g&s=19 I was torn between rtk query and react query but finally decided to use react query because it has better support for infinite queries and react suspense but I really like the rtk query style where all the query logic like fetching the data and stuff is done in a separate page and in the component we can just directly call the hook like this: useGetTodoQuery(id); Can someone please help me achieve that with react query and zod, it should be typesafe.
R. Alex Anderson πŸš€ (@ralex1993)
First time I saw this React Query pattern was @TkDodo's React Router blog post Brilliant πŸ‘ https://t.co/C2zTDOWit4
Likes
170
From R. Alex Anderson πŸš€ (@ralex1993)
Twitter
31 Replies
afraid-scarlet
afraid-scarletβ€’3y ago
Take a look at https://github.com/lukemorales/query-key-factory this helps maintain queries and api calls in 1 place
GitHub
GitHub - lukemorales/query-key-factory: A library for creating type...
A library for creating typesafe standardized query keys, useful for cache management in @tanstack/query - GitHub - lukemorales/query-key-factory: A library for creating typesafe standardized query ...
deep-jade
deep-jadeOPβ€’3y ago
Can you show me a repo where this is used? have you tried it ?
afraid-scarlet
afraid-scarletβ€’3y ago
i'm using it now for a large codebase. It's proved really effective. I got the idea from react query docs and tkdodo blog
afraid-scarlet
afraid-scarletβ€’3y ago
afraid-scarlet
afraid-scarletβ€’3y ago
so good to enforce convention in a codebase
afraid-scarlet
afraid-scarletβ€’3y ago
I;m also using https://orval.dev/ to generate all the api calls from a yaml (openapi) spec
orval
orval is able to generate client with appropriate type-signatures (TypeScript) from any valid OpenAPI v3 or Swagger v2 specification, either in yaml or json formats. 🍺
deep-jade
deep-jadeOPβ€’3y ago
Here's what i want: I want to be able to do something like this: const {data} = useQuery(fetchTodo(id)); or const {data = useFetchTodo(id); the link you shared has this: useQuery({ queryKey: todoKeys.list(state, sorting), queryFn: fetchTodos }) I don't understand what's the need for this, it's still too long. something like this
deep-jade
deep-jadeOPβ€’3y ago
No description
afraid-scarlet
afraid-scarletβ€’3y ago
yep, can do that. You declare a file called queries
export const users = createQueryKeys('users', {
all: null,
detail: (userId: string) => ({
queryKey: [userId],
queryFn: () => api.getUser(userId),
}),
});
export const users = createQueryKeys('users', {
all: null,
detail: (userId: string) => ({
queryKey: [userId],
queryFn: () => api.getUser(userId),
}),
});
THen you create custom hooks that consume these queries
export function useUserDetail(id: string) {
return useQuery(queries.users.detail(id));
};
export function useUserDetail(id: string) {
return useQuery(queries.users.detail(id));
};
deep-jade
deep-jadeOPβ€’3y ago
No description
afraid-scarlet
afraid-scarletβ€’3y ago
i have queries.ts per feature and then in each custom hook i get the specific query i want
deep-jade
deep-jadeOPβ€’3y ago
oh
afraid-scarlet
afraid-scarletβ€’3y ago
meaning the querykey declaration and api call is declared all in queries.ts or you can create 1 huge store with createQueryKeyStore
export const queries = createQueryKeyStore({
users: {
all: null,
detail: (userId: string) => ({
queryKey: [userId],
queryFn: () => api.getUser(userId),
}),
},
todos: {
detail: (todoId: string) => [todoId],
list: (filters: TodoFilters) => ({
queryKey: [{ filters }],
queryFn: (ctx) => api.getTodos({ filters, page: ctx.pageParam }),
contextQueries: {
search: (query: string, limit = 15) => ({
queryKey: [query, limit],
queryFn: (ctx) => api.getSearchTodos({
page: ctx.pageParam,
filters,
limit,
query,
}),
}),
},
}),
},
});
export const queries = createQueryKeyStore({
users: {
all: null,
detail: (userId: string) => ({
queryKey: [userId],
queryFn: () => api.getUser(userId),
}),
},
todos: {
detail: (todoId: string) => [todoId],
list: (filters: TodoFilters) => ({
queryKey: [{ filters }],
queryFn: (ctx) => api.getTodos({ filters, page: ctx.pageParam }),
contextQueries: {
search: (query: string, limit = 15) => ({
queryKey: [query, limit],
queryFn: (ctx) => api.getSearchTodos({
page: ctx.pageParam,
filters,
limit,
query,
}),
}),
},
}),
},
});
deep-jade
deep-jadeOPβ€’3y ago
will the data i receive typed? const {data} = useQuery(..) I need data here to be typed well
afraid-scarlet
afraid-scarletβ€’3y ago
it will infer the types from the api. so in getTodos or you can pass generics to the useQuery function (there are 4)
deep-jade
deep-jadeOPβ€’3y ago
oh
afraid-scarlet
afraid-scarletβ€’3y ago
Type-safe React Query
About the difference between "having types" and "being type-safe"...
deep-jade
deep-jadeOPβ€’3y ago
thanks, I will try that. btw, is there any repository that uses this library? i couldn't find one
afraid-scarlet
afraid-scarletβ€’3y ago
on that page
// βœ… typing the return value of fetchTodo

const fetchTodo = async (id: number): Promise<Todo> => {

const response = await axios.get(`/todos/${id}`)

return response.data

}


// βœ… no generics on useQuery

const query = useQuery({

queryKey: ['todos', id],

queryFn: () => fetchTodo(id),

})
// βœ… typing the return value of fetchTodo

const fetchTodo = async (id: number): Promise<Todo> => {

const response = await axios.get(`/todos/${id}`)

return response.data

}


// βœ… no generics on useQuery

const query = useQuery({

queryKey: ['todos', id],

queryFn: () => fetchTodo(id),

})
not entirely sure. I guess its worth asking the maintainer. I know i'm using it on a large goverment codebase are you using swagger at all?That helps by fully typing all the endpoints and models game changer!
deep-jade
deep-jadeOPβ€’3y ago
yes i have seen this article
deep-jade
deep-jadeOPβ€’3y ago
No description
deep-jade
deep-jadeOPβ€’3y ago
This is what I want, but I also want to use this with the factory libraray
afraid-scarlet
afraid-scarletβ€’3y ago
i guess you can use both. The query key factory is enabling the query key + api declaration in 1 place. zod is providing runtime schema validation
afraid-scarlet
afraid-scarletβ€’3y ago
Total TypeScript
When should you use Zod?
Use Zod to validate unknown inputs in your app, whether it's a CLI or a public API, to ensure that data entering your app is safe.
deep-jade
deep-jadeOPβ€’3y ago
oh, thanks I will check them out
stormy-gold
stormy-goldβ€’3y ago
zod in the query function is something you can always use, it's a completely separate topic to queryKeyFactories
stormy-gold
stormy-goldβ€’3y ago
FYI, in v5, we'll have queryOptions helpers to create type-safe key+fn+option pairs: https://twitter.com/TkDodo/status/1628453270401294337
Dominik πŸ‡ΊπŸ‡¦ (@TkDodo)
My attempt at a queryOptions api. Has the same structure as useQuery, is basically an identity function at runtime, but enables query-factories (like queryKey-factories, just better) I'm amazed that it also works for invalidateQueries. Thoughts?
Likes
184
From Dominik πŸ‡ΊπŸ‡¦ (@TkDodo)
Twitter
stormy-gold
stormy-goldβ€’3y ago
instead of Promise.resolve() in the image, you'd use fetch + zod.parse
deep-jade
deep-jadeOPβ€’3y ago
that's awesome, When will v6 release btw??
deep-jade
deep-jadeOPβ€’3y ago
also, https://tanstack.com/query/v4/docs/react/examples/react/suspense this example is not working for some reason
React Query Suspense Example | TanStack Query Docs
An example showing how to implement Suspense in React Query
stormy-gold
stormy-goldβ€’3y ago
It works for me

Did you find this page helpful?