48

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?

Tom Harvey
  • 3,681
  • 3
  • 19
  • 28

2 Answers2

105

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.

Tom Harvey
  • 3,681
  • 3
  • 19
  • 28
  • 1
    I think your `isLongEnough` should be like this: `const isLongEnough = searchString.length > 3;` – chety Aug 04 '22 at 07:44
2

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.

M1M6
  • 915
  • 3
  • 17
  • 33
  • Absolutely - I think the implementation you're suggesting there would be much simpler of an onClick as opposed to making an API call on render time. – Tom Harvey Sep 21 '22 at 11:20
  • One suggestion - there is a lot of code in your answer that's not strictly to do with answering the question. How to handle errors, the opinion that the results are saved to state, and rendered are all additional - making it an immediately usable code block, but it's harder to see the important thing you're calling out – Tom Harvey Sep 21 '22 at 11:22
  • 2
    Agree, at the same time you have a complete working component, you can take the code and play with it... – M1M6 Sep 21 '22 at 14:30