close icon
daily.dev platform

Discover more from daily.dev

Personalized news feed, dev communities and search, much better than whatโ€™s out there. Maybe ;)

Start reading - Free forever
Start reading - Free forever
Continue reading >

UseQuery React for Efficient Data Fetching

UseQuery React for Efficient Data Fetching
Author
Nimrod Kramer
Related tags on daily.dev
toc
Table of contents
arrow-down

๐ŸŽฏ

Learn how to efficiently fetch data in React using the useQuery hook from React Query. Simplify data fetching, caching, and synchronization for a responsive and up-to-date app.

If you're diving into React and looking to manage data fetching efficiently, useQuery from React Query is a game-changer. It simplifies data fetching, caching, and synchronization, making your app responsive and up-to-date. Here's a quick rundown:

  • Easy Data Fetching: Simplifies fetching data with a single hook.
  • Automatic Caching: Saves fetched data to reduce load times and server requests.
  • Background Updates: Keeps your app's data fresh without user intervention.
  • Error Handling: Smoothly manages errors and retries fetching if needed.
  • Developer Tools: Offers tools for debugging and optimizing your data fetching strategies.

To get started, install react-query, set up a QueryClient, and wrap your app with QueryClientProvider. Use useQuery in your components with a unique key and a fetch function. The hook handles the rest, from loading states to caching and updating the data.

Whether you're fetching a todo list from an API or implementing a dynamic search feature, useQuery handles the heavy lifting, letting you focus on building a great user experience. It's perfect for apps that need real-time data without the hassle of manual data management.

Understanding useQuery Hook Parameters and Return Values

Key Parameters

The useQuery hook needs two main things to work:

  • queryKey (required): Think of this as a unique label for your query. It can be a simple string or an array. This label helps the system remember and reuse your query efficiently.
  • queryFn (required): This is the function that actually goes and gets your data. It should be an async function that fetches data and returns it.

For instance:

useQuery(['users', 5], async () => {
  const res = await fetch('/api/users/5'); 
  return res.json();
});

In this example, ['users', 5] is the unique label, and the function fetches data for user ID 5.

Configuration Options

You can also tweak how useQuery works with a few options:

  • cacheTime: How long to keep data fresh even if it's not being used. Helps to not fetch data too often.
  • staleTime: How long before the fetched data is considered old. If it's old, it might fetch it again in the background.
  • refetchOnWindowFocus: Whether to get fresh data automatically when you come back to the window. Keeps data up-to-date.

Example:

useQuery('users', fetchUsers, {
  cacheTime: 10000,
  staleTime: 30000,
  refetchOnWindowFocus: true  
})

Return Values

useQuery gives back an object with helpful info:

  • status: Tells you if it's loading, if there was an error, or if it's successful
  • error: Any errors that happened
  • data: The data you fetched
  • isFetching: True if it's currently fetching data

Example:

const { status, data, error, isFetching } = useQuery('users', fetchUsers);

if (status === 'loading') {
  return <Spinner />;
}

if (status === 'error') {
  return <Message error={error} />;  
}

return <UserList data={data} />; 

This setup lets you manage loading screens, errors, and how to show your data in a simple way.

Implementing Core Data Fetching Use Cases

Fetching Todos from API

Here's a simple way to get a list of tasks (todos) from an online service using useQuery in your React app. This example also shows how to deal with waiting for the data to load and handling any errors that might pop up:

import { useQuery } from 'react-query';
import axios from 'axios';

function Todos() {
  const fetchTodos = () => axios.get('/api/todos');
  
  const { 
    isLoading, 
    error, 
    data: todos 
  } = useQuery('todos', fetchTodos);

  if (isLoading) {
    return <p>Loading...</p>;
  }

  if (error) {
    return <p>An error occurred: {error.message}</p>;
  }

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  );
}

This code neatly covers getting the data, showing a message while waiting, and dealing with errors. Once the data is fetched, it's ready to be shown.

Dynamic Search Implementation

This example shows how to set up a search feature in your React app where useQuery automatically looks for results based on what the user types:

import { useState } from 'react';
import { useQuery } from 'react-query';
import axios from 'axios';

function Search() {
  const [searchTerm, setSearchTerm] = useState('');

  const { 
    data: results,
    isLoading,
    error
  } = useQuery(
    ['search', searchTerm], 
    () => axios.get(`/api/search?query=${searchTerm}`),
    { enabled: !!searchTerm }
  );

  return (
    <>
      <input 
        type="text"
        value={searchTerm}
        onChange={e => setSearchTerm(e.target.value)} 
        placeholder="Search"
      />

      {isLoading && <p>Loading...</p>}

      {error && <p>Something went wrong: {error.message}</p>}
      
      <ul>
        {results.map(result => (
          <li key={result.id}>{result.name}</li>
        ))}
      </ul>
    </>
  );
}

In this setup, the search starts only when there's something typed in. The query updates and fetches new results whenever the search term changes.

Advanced Capabilities

Caching and Background Refetching

useQuery is really good at remembering data it has fetched before. So, if you ask for something it has already gotten, like a list of items, it wonโ€™t ask the server again but will give you the saved version. This makes your app quick for users.

But, if you always want the latest info, useQuery offers some cool features:

  • refetchInterval: This tells useQuery to get fresh data every few seconds.
  • refetchOnWindowFocus: This makes useQuery get new data when someone comes back to the app after looking at something else.

You can also ask for new data whenever you want with refetch().

Hereโ€™s how you can set it to get new data every five minutes:

const { data } = useQuery('products', fetchProducts, {
  cacheTime: 1000 * 60 * 60, // saved for 1 hour
  refetchInterval: 1000 * 60 * 5, // refreshes every 5 mins
})

The cool thing is, useQuery does all this without making your app slow or showing loading screens all the time.

Your app stays quick and keeps data up to date without users noticing much.

Implementing Paginated Data Fetching

When you need to show data in chunks, like a few items at a time, useQuery can help. Hereโ€™s a simple way to do it:

function Results({ page }) {
  const { resolvedData, latestData, status } = useQuery(
    ['results', page],
    () => fetch(`/results?page=${page}`).then(res => res.json()),
    { keepPreviousData: true }
  )

  if (status === 'loading') {
    return <Spinner />
  }

  if (status === 'error') {
    return <ErrorMessage />
  }

  return (
    <div>
      {/* If new data is there, show it, otherwise show old data */}
      {latestData?.results || resolvedData?.results}

      <button onClick={() => setPage(page + 1)}>
        Next Page
      </button>
    </div>
  )
}

What to remember:

  • Your page number is part of the query key.
  • keepPreviousData lets you keep seeing the old data until the new data is ready.
  • You can switch between the latest and the previously fetched data.

This makes handling pages simple without needing to juggle too much code.

sbb-itb-bfaad5b

Recommendations for Optimizing Performance

Organizing Queries for Maintainability

To make your code easier to handle as your React app gets bigger, it's a good idea to sort your queries in a way that makes sense. You could start your user-related queries with "user", and ones about posts with "post", and so on. This method helps keep things tidy and makes your code easier to read. It also helps React Query do its job better in managing when to grab or refresh data.

Here are some tips:

  • When naming your queries, using arrays can help you add more details like IDs.
  • Try to keep the names short but clear.
  • Make sure you're consistent in how you name them across your React JS project.

Tuning Cache Configurations

Deciding how long to keep data and when to get new data is about finding a balance. You want your app to have the latest info without slowing it down. Here's what you can do:

Frequently Changing Data

For data that changes a lot, like stock prices:

  • Use a short cacheTime
  • Set refetchInterval to regularly update
  • Keep staleTime short

This way, your app gets updates without you having to do much.

Slowly Changing Data

For data that doesn't change much, like blog posts:

  • You can keep it in the cache longer by increasing cacheTime
  • Set a longer staleTime so it doesn't check for updates too often

This reduces the number of times your app needs to ask for data, which can help it run smoother.

User-Dependent Data

For data that's different for each user, like profiles:

  • Turn on refetchOnWindowFocus
  • Use shorter times for both cacheTime and staleTime

This means the app will check for updates when someone comes back to it, keeping things fresh.

Using React Query Devtools to watch how your queries work can help you adjust these settings for each type of data. Getting this right can make your app faster and ensure it always has up-to-date information.

Debugging Techniques

When you're working with React Query in your React applications, sometimes things don't go as planned. Here's how you can figure out what's going wrong and fix it.

Leveraging React Query Devtools

React Query

React Query Devtools are super helpful for seeing what's happening with your queries:

  • Query Inspection: You can see all sorts of details like what queries are running, if they're working or stuck, and what data you're getting back. This is great for spotting problems.
  • Query Profiling: This feature lets you see how fast your queries are running and how much data they're using. It's useful for finding queries that are slowing things down.
  • Mutation Tracking: It also shows you how changes (mutations) in your data affect your queries, like causing them to fetch data again.

Adding Devtools to your project early on can really help you keep an eye on your queries and fix issues fast.

Logging Query States

Another way to figure out what's going wrong is by adding logs to your queries. Here's an example:

useQuery('todos', fetchTodos, {
  onError: (err) => console.log(err),
  onSettled: () => console.log('Query settled'),
})
  • onError will print out errors if something goes wrong.
  • onSettled tells you when the query has finished, regardless of whether it was successful or not.
  • You can also add logs in your components to see what's happening when data is loading or when there's an error.

Adding these logs helps you see exactly where things are going off track, without making your code complicated.

Conclusion

The useQuery hook from React Query makes fetching data in React apps a lot easier. This article covered how it helps automatically handle data fetching, caching, and updating. Let's quickly go over the main points:

  • Setting up queries: It's about telling useQuery what data you need by giving each query a unique name and a way to fetch that data.
  • Dealing with data: useQuery makes it simple to manage loading, errors, and displaying the fetched data.

We looked at examples like getting a todo list from an API and creating a search feature that reacts to user input. These show how useQuery can handle different data fetching needs.

useQuery also has advanced features for better performance, like smart caching and updating data in the background. Adjusting these settings helps make your app faster and keeps the data fresh.

For more complicated tasks, useQuery supports things like loading data in pages or based on other queries. Plus, its Devtools help you spot and fix problems by showing you how your queries are doing in real-time.

While useQuery does a lot of the heavy lifting, it's still important to organize your queries well and use the right settings for caching and updating data. This ensures your app works smoothly.

In short, useQuery is a great tool for any React developer. It simplifies dealing with data, so you can focus more on building cool features for your app. As apps get more interactive and data-driven, useQuery is becoming a key tool for creating better user experiences with less hassle.

How do you optimize data fetching in react?

Using React 18 for Better Data Fetching

React 18 lets your components start getting data without stopping the rest of your app from working. This means users can still use your app while it's fetching new data.

Here are some smart ways to fetch data in React apps:

  • Use tools like React Query or SWR for smart data saving and updating.
  • Turn on Concurrent Mode and Suspense with React 18.
  • Only load parts of your app when needed to avoid asking for too much data at once.
  • Break down data requests into smaller parts instead of getting everything at once.
  • Wait a bit before making search requests or similar actions to prevent asking for the same thing multiple times.
  • Get data ready ahead of time if you know you'll need it soon.

What is the best way to fetch data in react JS?

Here's how to get data in React:

  • React Query or SWR - These tools help manage data saving, avoid asking for the same data too much, and update data quietly.
  • useEffect hook - Use this to get data after your component is ready and keep it in the app's state.
  • Custom Hooks - Make your own hooks to reuse the data-fetching logic.
  • React Suspense + Fetch - Use Suspense to handle loading or errors when fetching data.
  • Axios - A handy tool for making web requests from your app or browser.

React Query and SWR are becoming popular, while useEffect and Suspense give you more control.

How do you use react query for fetching data?

react query

Getting data with React Query is easy. Here's a quick guide:

const { data, error, isLoading } = useQuery('posts', async () => {
  const res = await fetch('/api/posts')
  return res.json()
})

if (isLoading) return 'Loading...'

if (error) return 'An error has occurred: ' + error.message

return (
  <ul>
    {data.map(post => (
      <li key={post.id}>{post.title}</li>
    ))}
  </ul>
)

Important points:

  • Give a unique key and fetch function to useQuery.
  • It automatically handles loading, errors, and saving data.
  • Data is ready in the 'data' variable.
  • Components update by themselves when data changes.

This tool takes care of the tricky parts of getting data, so you can focus on your app's look.

When should we use useQuery?

useQuery

Use the useQuery hook from React Query for:

  • Basic data fetching - It makes getting data easier.
  • Saving data - Automatically saves data to avoid asking for it again.
  • Updating data in the background - Keeps your data fresh without interrupting the user.
  • Sharing data between components - Uses a central place to keep data so every part of your app can access it.
  • Showing loading and error messages - Tells you when it's getting data or if something went wrong.
  • Getting data ready before you need it - Starts fetching data before it's actually needed.

In short, useQuery is a helpful tool for dealing with web data in React apps, making things simpler for you.

Related posts

Why not level up your reading with

Stay up-to-date with the latest developer news every time you open a new tab.

Read more