152

I am new to this react-query library.

I know that when I want to fetch data, with this library I can do something like this:

const fetchData = async()=>{...}

// it starts fetching data from backend with this line of code
const {status, data, error} = useQuery(myKey, fetchData());

It works. But how to trigger the data fetching only when a button is clicked? , I know I probably could do something like <Button onPress={() => {useQuery(myKey, fetchData())}}/> , but how to manage the returned data and status...

Nicolas Hevia
  • 15,143
  • 4
  • 22
  • 31
Leem.fin
  • 40,781
  • 83
  • 202
  • 354

9 Answers9

183

According to the API Reference, you need to change the enabled option to false to disable a query from automatically running. Then you refetch manually.

// emulates a fetch (useQuery expects a Promise)
const emulateFetch = _ => {
  return new Promise(resolve => {
    resolve([{ data: "ok" }]);
  });
};

const handleClick = () => {
  // manually refetch
  refetch();
};

const { data, refetch } = useQuery("my_key", emulateFetch, {
  refetchOnWindowFocus: false,
  enabled: false // disable this query from automatically running
});

return (
  <div>
    <button onClick={handleClick}>Click me</button>
    {JSON.stringify(data)}
  </div>
);

Working sandbox here  

Bonus: you can pass anything that returns a boolean to enabled. That way you could create Dependant/Serial queries.

// Get the user
const { data: user } = useQuery(['user', email], getUserByEmail)
 
// Then get the user's projects
const { isIdle, data: projects } = useQuery(
  ['projects', user.id],
  getProjectsByUser,
  {
    // `user` would be `null` at first (falsy),
    // so the query will not execute until the user exists
    enabled: user,
  }
)
Nicolas Hevia
  • 15,143
  • 4
  • 22
  • 31
  • 1
    second time api call not wotking on button click – Rajesh N Feb 19 '21 at 06:48
  • Probably out-of-scope of this question. Check codesandbox and add console.logs in different parts (resolve, button, etc). It works more than once. Probably something else in your component is not working. Use [devtools](https://react-query.tanstack.com/devtools) and check how your cached responses act (are they refreshing when you click your button?) – Nicolas Hevia Feb 19 '21 at 07:54
  • check in console too its not working https://i.stack.imgur.com/Ue9Z9.png – Rajesh N Feb 20 '21 at 08:20
  • That is an unrelated api call from codesandbox, not from my code. The example has no actual API calls because it's an emulated fetch api. I'm actually just returning a promise (hence `emulateFetch ` name). React-query expects a promise, and since this was just an example it made sense to be hacky and not expect an actual response from free apis which might fail in the future and leave a future user with the same doubt without knowing what is going on. Check my example again, I've added a log. – Nicolas Hevia Feb 20 '21 at 10:36
  • 14
    Why not using useMutation instead of useQuery? – otto Apr 15 '21 at 10:20
  • 3
    If it helps someone, `refetch()` returns a promise and can resolve it to get the data of the response if you want to do something synchronously. – Nikhil Singh Aug 05 '21 at 15:23
  • 9
    We cannot use mutations for a get call, we can use them for put/post/delete calls https://react-query.tanstack.com/guides/mutations – AD B Aug 30 '21 at 16:57
  • As per the API reference, you can use `useLazyQuery` for example: `` ... const [getLogin, { loading, error, data }] = useLazyQuery("login", LOGIN_USER) ... return( ) `` – tufac2 Nov 28 '21 at 07:17
  • I need to call the refect on input change event is it possible to do that –  Dec 09 '21 at 08:19
  • 3
    @ADB I can't find anything in the documentation that says "useMutation cannot be used with get request", can you point me to the right place? – Bruce Sun Jun 06 '22 at 02:02
  • I read it somewhere on this blog, if you got time please checkout this blog: https://tkdodo.eu/blog/mastering-mutations-in-react-query – AD B Jun 10 '22 at 02:51
  • 1
    I didn't want to comment because the comment list is big enough, but from Tanner: "The difference is the flow of data. useQuery is used to query async data, useMutation is used to mutate it. Or in the traditional CRUD speak: Read: useQuery Create/Update/Delete: useMutation". – Nicolas Hevia Jun 10 '22 at 15:06
  • It would be reasonable to want to change the parameters of the query when the button is clicked too. But this doesn't look supported. – Ben Butterworth Jul 12 '23 at 04:21
  • 1
    This works but is [not recommended by the primary maintainer](https://github.com/TanStack/query/discussions/5820#discussioncomment-6604337). Instead use `queryClient.fetchQuery()` – heez Aug 11 '23 at 22:51
26

You have to pass the manual: true parameter option so the query doesn't fetch on mount. Also, you should pass fetchData without the parentheses, so you pass the function reference and not the value. To call the query you use refetch().

const {status, data, error, refetch} = useQuery(myKey, fetchData, {
      manual: true,
    });

const onClick = () => { refetch() }

Refer to the manual querying section on the react-query docs for more info https://github.com/tannerlinsley/react-query#manual-querying

Kevin Gelpes
  • 581
  • 4
  • 6
  • 29
    I think "manual" (that still exists) is replaced by "enabled"(in V2). https://github.com/tannerlinsley/react-query#disabling-or-pausing-a-query – rphlmr Jun 30 '20 at 08:46
16

Looks like the documentation changed and is missing the manual querying section right now. Looking at the useQuery API however, you'd probably need to set enabled to false, and then use refetch to manually query when the button is pressed. You also might want to use force: true to have it query regardless of data freshness.

Esxiel
  • 171
  • 2
5

At first react query gives us enabled option and by default it is true

const fetchData = async()=>{...}

const {status, data, error , refetch} = useQuery(myKey, fetchData() , {
enabled : false
}
);

<button onClick={() => refetch()}>Refetch</button>
Build Though
  • 300
  • 3
  • 9
  • r u sure that enabled is true by default? – HackerMF Mar 15 '23 at 16:09
  • Yah if enabled is not true then how can react query fetch data in initial render look up to react query docs they purely assign that enabled is true it is pretty common right cause we always want the data in initial render so enabled is always true. – Build Though Mar 16 '23 at 05:04
4

You can try this version:

const fetchData = async()=>{...}

// it starts fetching data from backend with this line of code
const {status, data, error, refetch } = useQuery(
myKey, 
fetchData(),
{
  enabled: false,
}
);
const onClick = () => { refetch() }
// then use onClick where you need it

From documentation Doc:

enabled: boolean

  • Set this to false to disable this query from automatically running.
  • Can be used for Dependent Queries.

refetch: (options: { throwOnError: boolean, cancelRefetch: boolean }) => Promise<UseQueryResult>

  • A function to manually refetch the query.
  • If the query errors, the error will only be logged. If you want an error to be thrown, pass the throwOnError: true option
  • If cancelRefetch is true, then the current request will be cancelled before a new request is made
Vitalii Andrusishyn
  • 3,984
  • 1
  • 25
  • 31
3

There is another way to do this that also works if you want to trigger multiple refetches.

const [fetch, setFetch] = useState(null);
const query = useQuery(["endpoint", fetch], fetchData);

const refetch = () => setFetch(Date.now());

// call the refetch when handling click.

If you want to refetch multiple entities you could have a top level useState that is called for instance fetchAll and:

...
const query = useQuery(["endpoint", fetch, fetchAll], fetchData);
...

and this code will also trigger if you press a button to fetch all.

Artur Carvalho
  • 6,901
  • 10
  • 76
  • 105
2

If the key is the same, then use refetch(), if the key is different then use useState to trigger the query.

For example:

const [productId, setProductId] = useState<string>('')
const {status, data, error, refetch} = useQuery(productId, fetchData, {
      enable: !!productId,
    });

const onClick = (id) => { 
if(productId === id) {
  refetch() 
}
else {
 setProductId(id)
}


}
Will
  • 325
  • 2
  • 9
1

There are two problems with refetch():

  • it will send a request to the server even if the response is already cached and the staleTime has not expired
  • you can't pass params to it

If you need to make a query on an event like onClick or onChange, use queryClient.fetchQuery().

function MyComponent() {
  const queryClient = useQueryClient();

  const handleOnClick = async () => {
    const data = await queryClient.fetchQuery({ queryKey, queryFn });
  }

  return <button onClick={handleOnClick}>My Button</button>;
}

You can see the creator and current maintainer recommending this approach.

heez
  • 2,029
  • 3
  • 27
  • 39
-11

you can use useLazyQuery()

import React from 'react';
import { useLazyQuery } from '@apollo/client';

function DelayedQuery() {
   const [getDog, { loading, error, data }] = useLazyQuery(GET_DOG_PHOTO);

   if (loading) return <p>Loading ...</p>;
   if (error) return `Error! ${error}`;

   return (
      <div>
         {data?.dog && <img src={data.dog.displayImage} />}
         <button onClick={() => getDog({ variables: { breed: 'bulldog' } })}>Click me!</button>
      </div>
   );
}

reference: https://www.apollographql.com/docs/react/data/queries/#manual-execution-with-uselazyquery

  • 1
    This is wrong answer because it uses apollo client which is a different library from react-query and doesnt apply in this context. Plus that is graphql the question is for REST API. – Vignesh S May 02 '22 at 18:19