Tom Harvey
Tom Harvey

Reputation: 4332

Conditionally calling an API using React-Query hook

I am using react-query to make API calls, and in this problem case I want to only call the API if certain conditions are met.

I have an input box where users enter a search query. When the input value is changed, a search server is called with the contents of the input as the search query ... but only if the input value is more than 3 chars long.

In my react component I'm calling:

const {data, isLoading} = useQuery(['search', searchString], getSearchResults);

And my getSearchResults function will, conditionally, make an API call.

const getSearchResults = async (_, searchString) => {
    if (searchString.length < 3)
        return {data: []}

    const {data}  = await axios.get(`/search?q=${searchString}`)
    return data;
}

We can't use a hook inside a conditional - so I put the condition into my API calling function.

This almost works. If I enter a short query string, there is no API request made and I get an empty array back for the data. Yay!

But - isLoading will flip to true briefly - even though there is no HTTP request being made. So my loading indicator shows when there is no actual network activity.

Am I misunderstanding how to best solve my use case, is there a way to enure that isLoading will return false if there is no HTTP activity?

Upvotes: 65

Views: 59497

Answers (2)

Tom Harvey
Tom Harvey

Reputation: 4332

The key was to use Dependent Queries

So, in my main component, I create a boolean and pass that to the enabled option of the useQuery hook:

const isLongEnough = searchString.length > 3;
const {data, isLoading} = useQuery(['search', searchString], getSearchResults, {enabled: isLongEnough});

and the API calling method is simply the API call - not any conditional:

const getSearchResults = async (_, searchString) => {
    const {data} = await axios.get(`/search?q=${searchString}`);
    return data;
}

The docs describe dependent queries as a solution for loading data from subsequent API endpoints, but the enable option can accept any boolean. In this case - if the search query string is long enough.

Upvotes: 135

M1M6
M1M6

Reputation: 1013

There's another option which is to use queryClient.fetchQuery API, which gives you the ability to conditionally call the query to fetch the data.

function Example2() {
  const [data, setData] = useState(null);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);

  if (isLoading) return "Loading...";

  if (error) return "An error has occurred: " + error;

  return (
    <div>
      <button
        onClick={async () => {
          try {
            setIsLoading(true);
            const posts = await queryClient.fetchQuery(
              ["postsUsingFetchQuery"],
              {
                queryFn: () =>
                  axios
                    .get("https://jsonplaceholder.typicode.com/posts")
                    .then((res) => res.data)
              }
            );
            setData(posts);
          } catch (e) {
            setError(e);
          }
          setIsLoading(false);
        }}
      >
        Fetch posts using fetchQuery{" "}
      </button>
      <h1>Posts</h1>
      {data?.map((post) => {
        return (
          <div style={{ display: "flex" }}>
            <span>{post.id}-&nbsp;</span>
            <div>{post.title}</div>
          </div>
        );
      })}
    </div>
  );
}

On the button click handler, we’ve added the implementation to fetch the posts using queryClient.fetchQuery.

You can read more from this link.

Upvotes: 2

Related Questions