useLodaerData()
.✅ improves the user experience by fetching as early as possible ✅ good developer experience of co-located data fetching and rendering
❌ loaders are called on every page visit, so fetching too often
you can solve this by combining with React Query. Use the router for fetching data early, and React Query for caching and keeping the data fresh.
That is, use React Router to fetch data early for the first time, and React Query to 1. use cached data and 2. invalidate queries when mutating.
Code is like below.
import { useQuery } from '@tanstack/react-query'
import { getContact } from '../contacts'
// ⬇️ define your query
const contactDetailQuery = (id) => ({
queryKey: ['contacts', 'detail', id],
queryFn: async () => getContact(id),
})
// ⬇️ needs access to queryClient
export const loader =
(queryClient) =>
async ({ params }) => {
const query = contactDetailQuery(params.contactId)
// ⬇️ return data or fetch it
return (
queryClient.getQueryData(query.queryKey) ??
(await queryClient.fetchQuery(query))
// or like below after v4.0
await queryClient.ensureQueryData(query)
)
}
export default function Contact() {
const params = useParams()
// ⬇️ useQuery as per usual
const { data: contact } = useQuery(contactDetailQuery(params.contactId))
// render some jsx
}
ensureQueryData
async function to get an existing query’s cached data.
if the query not exist, queryClient.fetchQuery
will be called
basically same as below
queryClient.getQueryData(query.queryKey) ??
(await queryClient.fetchQuery(query))
⚠️ access to queryClient not importing it
const queryClient = new QueryClient()
const router = createBrowserRouter([
{
path: '/',
element: <Root />,
children: [
{
path: 'contacts',
element: <Contacts />,
children: [
{
path: 'contacts/:contactId',
element: <Contact />,
// ⬇️ pass the queryClient to the route
loader: contactLoader(queryClient),
},
],
},
],
},
])