TkDodo / blog-comments

6 stars 1 forks source link

blog/practical-react-query #12

Closed utterances-bot closed 2 years ago

utterances-bot commented 3 years ago

Practical React Query | TkDodo's blog

Let me share with you the experiences I have made lately with React Query. Fetching data in React has never been this delightful...

https://tkdodo.eu/blog/practical-react-query

tannerlinsley commented 3 years ago

Fantastic!!!

johnnyreilly commented 3 years ago

This is a tremendous write up! Thanks!

johnnyghost commented 3 years ago

Good stuff! Thanks 💪

hrafnkellpalsson commented 3 years ago

Great post! Hadn't considered using the enabled option for anything else then dependent queries, very neat!

d-ivashchuk commented 3 years ago

Great write up 🔥

frycz commented 3 years ago

Thanks TkDodo!

GrooyaO commented 3 years ago

Thank you. Super clear! 💡

AjaxSolutions commented 3 years ago

Good writeup. I wasn't clear before on what is the difference between isFetching and isLoading.

DevAbas commented 3 years ago

Thanks! Super ❤️👌

willnguyen1312 commented 3 years ago

Love the way you explain things, couldn't be better ❤️

anoopvasudevan commented 3 years ago

This! Great article. Clarified a lot of confusions.

ArianHamdi commented 3 years ago

Thanks tkdodo for this amazing article. for a server state that can be editable by user, can I setState in onSuccess ? in this way we are not forced to wait for data and pass it as initial state to the component.

TkDodo commented 3 years ago

for a server state that can be editable by user, can I setState in onSuccess ?

you can, but be aware that onSuccess is called for every successful background refetch as well, thus calling setState. I would still stick to a clear separation as outlined here: https://tkdodo.eu/blog/practical-react-query#keep-server-and-client-state-separate

qkangusc commented 3 years ago

This is exactly what I am looking for. Thank you tkdodo!

I wrapped useQuery inside custom hook and use it in many places in my code. One thing I noticed is that I log the success event in onSucess handler, so every time the fetch succeed, all the observers call onSuccess and the success event is log multiple times. Is there a way to only send the log once?

TkDodo commented 3 years ago

Is there a way to only send the log once?

we have on open PR to add a global onSuccess handler to the queryCache. Feel free to try it out and provide feedback: https://github.com/tannerlinsley/react-query/pull/2404

phutngo commented 3 years ago

An amazing write-up! A gem. Very rare do I see articles having this level of quality, packed with practical insights wrapped in logically tight prose that are not only easy but a joy to consume.

TkDodo commented 3 years ago

thank you very much @phutngo, your comment truly made my day ❤️

jmsims2 commented 3 years ago

I've been loving this series, so much great info! Question on custom hooks, is there any reason to destructure in the custom hook to return just what you plan on using or should you just return useQuery and destructure when you use it?

const useTodos = () => useQuery(getTodos)

vs

const useTodos = () => {
  const {data, error, isLoading} = useQuery(getTodos)
  return {data, error, isLoading}
}
csvinhal commented 3 years ago

Awesome!! Great article!

TkDodo commented 3 years ago

@jmsims2 I personally like to return everything from useQuery, to keep the return values the same for every custom hook. Especially if you use tracked queries, every consumer can decide what they need and react-query will render optimize accordingly. But generally speaking, it doesn't matter much and comes down to personal preference :)

mignotju commented 3 years ago

Thank you very much for this article, it is very clear ! 👍

baba-ali-graph commented 3 years ago

Amazing article, this was very helpful. I am using react-query for a very large scale application React application. My problem is, I would like to run some function on the onSuccess and onSettled events of the queries and mutations. Some of them containe generic logic while others are specific. I would like to create a base query/mutation hook and extend these for the feature-specific queries/mutations, to avoid duplication. However, I'm still not sure if this is the right way to go. Are there any pointers you can give on handling this issue?

TkDodo commented 3 years ago

@AleeyCreative what kind of logic are we talking about? Please keep in mind that the callbacks will be executed on every background update, and they will also execute once per observer. So if you use the same useQuery hook (or custom hook that wraps useQuery) multiple times, the callbacks will execute multiple times

Miravicson commented 3 years ago

As amazing as it is practical. I would love to submit a PR to fix a typo I spotted if I could. @TkDodo.

TkDodo commented 3 years ago

@Miravicson yes please do, the blog is open source: https://github.com/TkDodo/blog

Miravicson commented 3 years ago

@TkDodo, I created a PR. now the article is truly perfect 😀. https://github.com/TkDodo/blog/pull/50

alimirmohammad commented 3 years ago

Thank you for the great article. I just started to use react-query for my new project, and I have some questions about how to use it. I have an OAuth2.0 flow for my authentication, so it is redirect-based. When I get the code in my redirect page I request for a token. Tokens are valid for 15 minutes. The API also supports refresh tokens. Right now I handle the authentication part using Redux Toolkit. I request the token after redirect, save it in Redux store using createAsyncThunk function. Then I set an interval with the interval time of 14 minutes to request for refresh tokens. I also store token data and its expiration time in local storage, and whenever token data changes I update the local storage. So if the user refreshes the page, they are automatically logged in without sending a request for token.

First of all, I need to know if token information is considered server state? Does it make sense to move this logic into react-query? Is that even feasible? Because I don't need background refetch when screen is focused or data is stale, I just need that refresh token request every 14 minutes, and I don't want any additional and unnecessary requests for tokens. If it makes sense to do it, how should I do it, reading and writing to local storage and just use the token wherever in my app?

TkDodo commented 3 years ago

@alimirmohammad It's an interesting question. getting a refresh token could be seen as mutation, given that it creates a token (resource). Once you have it, do you really need to distribute it to all consumer? You could also attach it directly as a header to your "network request layer", e.g. axios.create. That could be a custom hook in itself.

Dan Abramov has a great example of making a useInterval hook: https://overreacted.io/making-setinterval-declarative-with-react-hooks/

if you wind up with a query, I would set staleTime to Infinity (and maybe cacheTime as well), and then use the refetchInterval option to refetch every 14 minutes.

alimirmohammad commented 3 years ago

@TkDodo, Thank you for the hint. I actually wound up using both mutations and queries. Because a mutation data is not meant to be read from several places as far as I know. Also, there is no method to set a mutation data in the cache, I need this for my auto login when the token is not expired. So I created a dummy query with a queryFn which is a promise that resolves to some object. And onSuccess of my mutations for token and refresh token I update the local storage and I set the data for the query. Also when app starts, I check and if there is a valid token, I set data for the query.

For using the token, I created a wrapper for the Fetch API (I don't use Axios because I need service-workers), and attach the token to my requests. For this, I exported the queryClient in a file, and imported it here, to read the token data.

I know it sounds imperative, but it works.

Also I created several custom hooks to get different parts of token data, with duplicated code, because TypeScript had a hard time inferring the type of the select option.

wtLau commented 2 years ago

Amazing! There's a lot to unpack in this article...

Pelv commented 2 years ago

Hi there, the Dependant Queries link has changed on the official doc. Here is the correct one: https://react-query.tanstack.com/guides/dependent-queries#_top

Moe2610 commented 2 years ago

@TkDodo I left a question for you on stackoverflow on how to implement long polling using react-query interesting enough there is nothing on the web related to the subject.

" Would like to ask for suggestions on implementing long Polling with useQuery. i.e. send a new request only when response comes back, and request will wait on the server until new events or some changes happens and maybe some time out. "

TkDodo commented 2 years ago

@Moe2610 thanks, I've seen the question, but I don't have an answer yet. I haven't worked with long polling yet and I don't immediately see how it would integrate with react-query, as there won't really be any smart refetching (as the connection is always open?). So maybe a simple useEffect + context to distribute the data would be sufficient...

Moe2610 commented 2 years ago

@TkDodo thanks for replay. The connection is not going to be always open. The process is as following:

  1. Client sends the first http request
  2. The request stays open on the server until an update is available for this client (server uses session-id and keep track of the time line for each client request)
  3. When an update is available the server will answer the request and the request closes
  4. Once the client receives response a new fetch/refetch request should be sent immediately (no timeout for near real time requirement).
  5. there should be no attempt to refetch when the connection is open (i.e when the previous request is waiting on the server for update) but only when a response is received.
  6. If an error happens a refetch should be fired immediately

I think this covers most of the required behavior. Could achieve with recursive fetch but want to use react-query eco system to avoid boilerplate and for consistency across the application.

TkDodo commented 2 years ago

The connection is not going to be always open.

Yes, I was phrasing that poorly, because always open would be websockets. I meant "de-facto" always open, because once you receive something and the server closes the connection, the client should open a new one immediately, like you outlined.

I think my reasoning still stands though. If we were to implement that from within the queryFn, the query would essentially always be in loading / fetching state. And we would need to turn off all retries. So similar as to React Query and WebSockets, we could keep that a bit outside of / orthogonal to RQ.

femicodes commented 2 years ago

lovely article 👏🏽

aantipov commented 2 years ago

Hi, I have a question about "initial-form-data" example: const { data } = useQuery('key', queryFn, { staleTime: Infinity })

Imagine, there are other queries with the same key on the page: const { data } = useQuery('key', queryFn)

Those other queries do background re-fetches which, at some point, yield new data. Will that new data appear in the first query with { staleTime: Infinity }?

How do staleTime and cacheTime work when there are multiple useQuery with the same key but different values for those options?

TkDodo commented 2 years ago

How do staleTime and cacheTime work when there are multiple useQuery with the same key but different values for those options?

that doesn't really make sense, which is why it's best to abstract them away into custom hooks. There is only one cache entry, so you can't have multiple cacheTimes. I guess the longer one wins?

aantipov commented 2 years ago

Thanks for the prompt answer!

What about staleTime? Is it the same?

And what about other options? Can't I have 2 queries on the same page with the same key but different options?

TkDodo commented 2 years ago

And what about other options? Can't I have 2 queries on the same page with the same key but different options?

you can, but it only makes sense for options that are specific to observers. For example, you can have to queries with different select, because every observer can pick their own things from the data via select.

you can also have two different staleTimes, because technically, that's an observer prop. As long as only one query is mounted, that staleTime is taken. However, once both are mounted, what should happen? At the end of the day, there is only one query to mark as stale. So we run two timers, and when the first one (the shorter one) finishes, we mark the cache entry as stale.

QzCurious commented 2 years ago

For typescript, I was wondering that if a custom hook sacrifices the ability to set options on every invokation (implicitly) of useQuery? I can't find a way to easily and correctly type options as if I:

 /* I want the options is infered */
const useTodosQuery = (state: State, options: any) => {
   /* 
    * Here the signature of useQuery is solved, options too.
    * Could it infer the type back to type of options of useTodosQuery?
    */
  useQuery(['todos', state], () => fetchTodos(state), options)
}
TkDodo commented 2 years ago

@QzCurious there are definitely ways to make it work, but it is often not the right abstraction. For example, options also contains a QueryKey, but you have that hardcoded in your example, so it wouldn't make sense to let the user of the custom hook pass that in as an option. Same goes for things like cacheTime or staleTime, where it doesn't make sense if multiple hook calls use different value.

To make everything work, you need to juggle 4 generics like react-query does it internally. You can read more in this discussion.

All of this is why I like to keep my custom hooks small in interface. Like, usually something like this:

const useTodosQuery = (state: State, options: { enabled: boolean, onSuccess: (todos: Todos) => void }) => {
  useQuery(['todos', state], () => fetchTodos(state), options)
}

or whatever other props I really need at the moment. Sometimes, this means I have to amend the options, but that doesn't happen very often. select is one I use a lot, so that would be, in TypeScript:

const useTodosQuery = <T = Todos>(state: State, select?: (todos: Todos) => T) => {
  useQuery(['todos', state], () => fetchTodos(state), { select })
}

But I don't think I've used a custom hook that needed more than 3 props or so. My usages of one query are mostly the same :)

QzCurious commented 2 years ago

@TkDodo Thanks for your advice. Great to know that in practical aspect of view, some of options are more likely to be "static" options. And only a few of options will be needed (at caller level).

Sowed commented 2 years ago

❤️

kremuwa commented 2 years ago

Thanks for a great article!

I'd like to ask what do you mean by:

You can keep all usages of one query key (and potentially type definitions) in one file.

If there is a mutation that updates e.g. the data under the todos key it will need to use the same query key as the query that downloads the todos. Would you suggest to write such a mutation in the same file?

TkDodo commented 2 years ago

Would you suggest to write such a mutation in the same file?

Yes, that's what I usually do. Even though I name it queries.ts, it often also contains mutations. It contains all react-query related things :)