TkDodo / blog-comments

6 stars 1 forks source link

blog/react-query-and-type-script #23

Closed utterances-bot closed 2 years ago

utterances-bot commented 3 years ago

React Query and TypeScript | TkDodo's blog

Combine two of the most powerful tools for React Apps to produce great user experience, developer experience and type safety.

https://tkdodo.eu/blog/react-query-and-type-script?utterances=b435597b2a9c58fa332c2615jllrscOW14fVS1v60n0O1dygX%2BCA7p%2BksVnlREfguN5WhmqBgs9LK8RdwuzgW20LtZbI8E96p4qHq2%2BISvhgyJUYr2Hqh0c5cae1SdnJEJBnM7BFNnQjLkT1bfQ%3D

huanguolin commented 3 years ago

That's really good! Thanks!

wobsoriano commented 3 years ago

Thanks!

Maadtin commented 3 years ago

In the explicit-id-check wouldn't TypeScript complain about it possibly not returning a Promise<Group> but a Promise<Error> or smth like that?

TkDodo commented 3 years ago

@Maadtin no, you only specific the type of resolved promises. Rejected promises don’t really have a type - you can reject with or throw anything.

knoefel commented 3 years ago

Very useful article! Thanks a lot! Keep up the great work on this blog!

phatmann commented 3 years ago

I was able to set the typing for the page parameter as follows:

type PageParam = MyData['nextPageUri']

export function useMyData() {
  const server = useServer()
  return useInfiniteQuery<MyData>(
    keys.all,
    ({ pageParam: nextPageUri }: QueryFunctionContext<QueryKey, PageParam>) =>
      fetchMyData(nextPageUri),
    {
      getNextPageParam: page => page.nextPageUri,
    }
  )

  async function fetchMyData(nextPageUri: string | undefined) {
    await server.get<MyData>(
        nextPageUri ?? '/my_data'
      )
  }
}
TkDodo commented 3 years ago

@phatmann cool, I'm actually doing something similar right now :)

chiptus commented 2 years ago

you can actually use generics to state axios return type

const {data} = axios.get<Type>(url)
// data is Type
AndrewThian commented 2 years ago

Thank you so much for the blogposts, it has been an absolute blast reading all of them. However, I do need some help with typing useQueries. I've been leveraging the query function context and it's been fantastic with single useQueries.

However, when dealing with multiple queries in useQueries array, I've been type assertion errors but I can't seem to figure out why. I'm definitely on typescript 4 and using the latest react-query packages that implement the recent updates to useQueries types

an example would be

const queries = useQueries([
    {
      queryKey: dashboardQueryKeys.keyNumbers({ startDateTime: new Date() }),
      queryFn: fetchDashboardAssetsSummary
    }
  ])

The error always seems to suggest the context function is not inferring the correct types i.e Type 'QueryFunctionContext<QueryKey, any>' is not assignable to type 'QueryFunctionContext<readonly ...

TkDodo commented 2 years ago

@AndrewThian can you spawn a quick typescript playground or codesandbox that shows the typing issues with useQueries you have been facing? I have to admit, the typings for useQueries are a beast and I frequently need to reach for help from the original author. Maybe there is a real issue with them...

AndrewThian commented 2 years ago

Thank you for replying at such a quick notice :) you're an absolute gem haha

I've attached a sandbox that sorta replicates the issues with typescript: sandbox

The offending type assertion is in the

const usePokemons = () => {
  return useQueries([
    {
      queryKey: queryKeys.pagination({ limit: 100, offset: 200 }),
      queryFn: fetchPokemon
    }
  ]);
};

Looking forward to your expertise! Thank you once again

TkDodo commented 2 years ago

thank you @AndrewThian . It seems to be an issue. The inferred context is just: QueryFunctionContext<QueryKey, any>. Can you open an issue in RQ with that reproduction, and I'll see what I (or someone else) can do :)

dd-jonas commented 2 years ago

With TypeScript 4.6, the destructured example works thanks to control flow analysis for destructured discriminated unions.

const { data, isSuccess } = useGroups()
if (isSuccess) {
  // ✅ data is narrowed to `Group[]` here
}
TkDodo commented 2 years ago

@dd-jonas yes, I'm aware and I plan on updating the blogpost. This is an amazing feat by the typescript team :)

kelvindecosta commented 2 years ago

Thank you for such an in-depth guide on using TypeScript with react-query!

I would like to implement a useGroups hook that takes an optional options argument like so:

function useGroups(options?: unknown) {
  return useQuery('groups', fetchGroups, options)
}

Is it possible to let TypeScript infer the type of options?

Thank you again for sharing your knowledge!

kelvindecosta commented 2 years ago

Hey!

Please ignore my previous comment :P

The main issue I had with using useQuery directly was that I felt the need to specify arguments as part of the queryKey and the queryFn. I saw myself writing the same object over and over again, which I wanted to avoid.

After reading your post on query keys, I realized there was a much easier way. Thank you so much!

earthnoob commented 2 years ago

Hi, I've just stumbled across your blog post and that got me thinking about something I've been stuck with recently, namely dehydration with SSR frameworks like NextJS.

I followed their examples for Next, but when I wanted to extract the data returned by DehydratedState, it gave me this:

function ArticleDetailPage({ dehydratedState }: PageProps): JSX.Element {
  return (
    <div>
      {/* Some default Next page stuff */}
      <Blog article={**dehydratedState.queries[0].state.data**.data[0] as Article} />
      {/* ^ Object is of type 'unknown'. */}
    </div>
  );
}

I have getStaticProps setup like so:

export const getStaticProps: GetStaticProps<Props, Params> = async (context) => {
  const { params } = context;
  const slug = params?.slug ?? '';

  const queryClient = new QueryClient();

  await queryClient.prefetchQuery<StrapiAPIResponse | APIError, Oops, StrapiAPIResponse | APIError, QueryKey>(
    'article-detail.get-article-detail',
    async () => {
      const response = await getArticleBySlug(slug);
      return response;
    },
  );

  return {
    props: {
      dehydratedState: dehydrate(queryClient),
    },
  };
};

Is there any way to tell ArticleDetailPage what the fetched data type from react query is?

TkDodo commented 2 years ago

@earthnoob the recommended approach is to pass the dehydratedState to the Hydrate component provided by React query. That will fill the cache so that you can access it with useQuery.

yonycalsin commented 2 years ago

awesome !

imbishalgiri commented 2 years ago

how to set type with useMutation()'s return mutation.mutate( {name, email, password} ) gives me error saying Argument of type '{ name: string; email: string; password: string; }' is not assignable to parameter of type 'void' i am perplexed

chiptus commented 2 years ago

@imbishalgiri mutation.mutate arguments map to the function you pass to useMutation. I guess your function was without any arguments (i.e useMutation(() => doSomeMutation)) while it should be useMutation({name, email, password})

TkDodo commented 2 years ago

@imbishalgiri it depends on the type of the mutationFunction (the first parameter to useMutation). Given:

const { mutate } = useMutation(
  (params: { name: string; email: string; password: string}) => someMutation(params)
)

you should be able to all it like:


mutate({ name, email, password })
imbishalgiri commented 2 years ago

thank you so much for your response. It worked!! and your response time was blazing fast but why am i not being able to destructure error

i mean after i do this

 const { mutate, error } = useMutation((newUser: User) => {
      return signup(newUser)
   })

it gives me error line below error

if (error?.response?.data?.field === 'email') {
         setError('email', {
            type: 'custom',
            message: error?.response?.data?.message,
         })
      }
TkDodo commented 2 years ago

My guess is because error is of type unknown, so you can't just access .response, not even with optional chaining. I think I have this covered in the blog post :)

imbishalgiri commented 2 years ago

already tried instanceOf but typescript still yelling at me ... looks like i should deep dive into typescript

thank you for your help tho