Loader

✅ 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

how to solve?

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
}

⚠️ 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),
          },
        ],
      },
    ],
  },
])

Actions