Hey folks!
If you recently worked with client side data fetching in Next.js, you probably heard of SWR. It comes with useSWR
, a React hook that makes all the complicated stuff in client side data fetching (caching, revalidation, focus tracking etc.) easy as pie.
You can implement it with just a few lines of code:
// Import the hook
import useSWR from 'swr'
// Define a custom fetcher function
const fetcher = (url) => fetch(url).then((res) => res.json())
function Profile() {
// Use the hook to fetch your data
const { data, error } = useSWR('/api/user', fetcher)
if (error) return <div>failed to load</div>
if (!data) return <div>loading...</div>
return <div>hello {data.name}!</div>
}
Easy, right? Well, it definitely is when you try to fetch an endpoint with no query parameters, like /api/user
. But when you try to pass a dynamic route parameter to your useSWR
hook, things can get a little bit tricky. I recently spent some time figuring out a solution for this, so I thought I should share my solution.
Let's say we have a dynamic user route under /pages/user/[id].js
, which should show a user profile based on the ID we pass as a route parameter.
The code to access that ID parameter would look like this:
// Import the useRouter hook from Next.js
import { useRouter } from 'next/router'
function Profile() {
// Use the useRouter hook
const router = useRouter()
// Grab our ID parameter
const { id } = router.query
return <div>user id: {id}</div>
}
If you open that page with a random ID (http://localhost:3000/user/42
i.e), you should see the ID on the rendered page (user id: 42
). Now, instead of just rendering that ID, let's fetch the user related to that ID from our API endpoint and render a profile page.
When I tried to do that, I thought I could just pass the ID parameter to the useSWR
hook and voilá – a beautiful profile page. The code looked like that:
import useSWR from 'swr'
import { useRouter } from 'next/router'
const fetcher = (url) => fetch(url).then((res) => res.json())
function Profile() {
const router = useRouter()
const { id } = router.query
const { data, error } = useSWR(`/api/user/${id}`, fetcher)
if (error) return <div>failed to load</div>
if (!data) return <div>loading...</div>
return <div>hello {data.name}!</div>
But then the error messages came in – something obviously didn't work, my component just won't fetch the user. What happened here? When I had a look into the network tab, I noticed that the ID parameter wasn't passed to the fetch call – instead it said undefined
. But why? The ID was clearly there, so what the heck happened here?
The answer is in the Next.js Docs:
Pages that are statically optimized by Automatic Static Optimization will be hydrated without their route parameters provided, i.e
query
will be an empty object ({}
). After hydration, Next.js will trigger an update to your application to provide the route parameters in thequery
object.
Since I didn't use getServerSideProps
or getStaticProps
on that page, Next turned on Automatic Static Optimization for it – which means the dynamic parameters from router.query
are not available until the hydration process has finished. Before, query
is just an empty object – that's why the network tab said undefined
.
So how can we tell useSWR
to wait until our dynamic route parameter is ready?
TL;DR
import useSWR from 'swr'
import { useRouter } from 'next/router'
const fetcher = (url) => fetch(url).then((res) => res.json())
function Profile() {
const router = useRouter()
const { id } = router.query
// Use a ternary operator to only fetch the data when the ID isn't undefined
const { data, error } = useSWR(id ? `/api/user/${id}` : null, fetcher)
if (error) return <div>failed to load</div>
if (!data) return <div>loading...</div>
return <div>hello {data.name}!</div>
This way our page now initially renders Loading...
, and as soon as the hydration process has finished it fetches the user data and renders the profile.
I hope this little explanation could help you!
Top comments (10)
LOL I went through this exact discovery journey myself this morning, scratching my head as per why the router param was undefined..
I slowly but surely landed on this exact solution when finding this discussion:
github.com/vercel/next.js/discussi...
And of course, 2 minutes later, I land on your awesome post which is exactly what I was looking for all along 😅
Thanks Simon! Haha, yeah, this discussion was also what helped me when I had that problem.
Big fan of your work with Tailwind Labs btw, keep up the good work! 🤙🏼
You know, I actually just had a placeholder for my dynamic id in my code... but I would have been stumped on that here in about 10 minutes. So thanks for that advance heads up brother!
Thanks dude, awesome!
Was dealing with this issue all day long. Glad I found your article!
You're welcome Phalange! Glad I could help you :) In which field of web dev are you working?
Good to know this, thank you for sharing! Next.js like any other framework has some hidden parts that are not obvious and have to be learnt. Good catch!
You're welcome! :)
Thank you mate. I'm using react-query facing the same issue. This helped.
You're welcome my friend! Happy coding :)
Thanks a lot for trick where we get rid the Automatic Static Optimization! 🔥❤️