useQuery is a React Query hook for fetching asynchronous server data while exposing data, status, and error state. Unlike a simple fetch hook, it identifies results with a query key, reuses cached data, and can refresh it in the background. section 10.3.3 section 10.3.1
Use useQuery by following this progression:
- Wrap the component tree in QueryClientProvider with a shared QueryClient so descendant hooks can access the cache. section 10.3.2
- Pass a query key and an asynchronous function that returns the requested data. section 10.3.3
- Read data, status, and error to render loading, failure, and successful UI states. section 10.3.3 section 12.2
- Use stable keys that describe the request; strings suit shared collections, while serialized arrays can include identifiers and date ranges. section 10.3.3
- Add a configuration object when needed, including initialData from another cached query to avoid an unnecessary loading spinner. section 10.3.4
Fetching bookables
const {data: bookables = [], status, error} = useQuery( "bookables", () => getData("http://localhost:3001/bookables") ); Calling the same key from the Bookables and Bookings views lets React Query return cached bookables immediately and fetch the latest data in the background. For request-specific data, use a key such as ["bookings", bookableId, start, end].
Using cached initial data
const queryClient = useQueryClient(); const {data, isLoading} = useQuery( ["bookable", id], () => getData(`/bookables/${id}`), { initialData: queryClient.getQueryData("bookables") ?.find(b => b.id === parseInt(id, 10)) } ); The edit form can populate immediately from the cached collection while the individual query obtains current server data. section 10.3.4
Listing 12.10 The BookablesView component with Suspense integration
import {Link, useParams} from "react-router-dom";
import {FaPlus} from "react-icons/fa";
import {useQuery} from "react-query";
import getData from "../../utils/api";
import BookablesList from "./BookablesList";
import BookableDetails from "./BookableDetails";
// no need to import PageSpinner
export default function BookablesView () {
const {data: bookables = []} = useQuery(
"bookables",
() => getData("http://localhost:3001/bookables"),
{
suspense: true #1
}
);
const {id} = useParams();
const bookable = bookables.find(
b => b.id === parseInt(id, 10)
) || bookables[0];
// no status checks or loading/error UI #2
return ({/* unchanged UI */});
}
#1 Pass a config object with suspense set to true.
#2 Remove the status-checking code for loading and error states.
Compare ordinary status-based rendering with suspense: setting suspense to true makes the query throw promises and errors for surrounding Suspense and ErrorBoundary components. section 12.2

