Reputation: 31365
I've got a page on my Next.js app that does the following:
/search?q=search+slug
router.query
to get the router.query.q
valuePS: I'm using Redux
const dispatch = useDispatch();
const router = useRouter();
const query = router.query as { q: string };
const queryString = query.q;
console.log("SEARCH CONTAINER");
console.log(`queryString: ${queryString}`);
useEffect(() => {
dispatch(THUNK.LOAD(queryString));
return () => { dispatch(ACTION.RESET_STATE()); };
},[dispatch,queryString]);
See the useEffect
. In theory is should run only once for every queryString
(which is actually req.query.q
).
But I was getting duplicated THUNK.LOAD
actions. That's why I've added the console.log()
there.
And this is what it's logging out:
And then:
And this is why I'm getting duplicated dispatches. Of course I can check for if (queryString)
before dispatching, or maybe I can get it from window.location.search
. But I am surprised the router.query.q
comes as undefined
on the first place. How is that even possible? Why would the req.query
object be populated asynchronously? What is the explanation for this?
Upvotes: 16
Views: 23340
Reputation: 31365
Just found out what is the solution to my problem:
From: https://nextjs.org/docs/api-reference/next/router#router-object
isReady
:boolean
- Whether the router fields are updated client-side and ready for use. Should only be used inside ofuseEffect
methods and not for conditionally rendering on the server.
This is what happens to the router.query
on client when you hit /search?q=XXX
.
1st render
router.isReady: false
router.query: {}
Subsequent renders
router.isReady: true
router.query: {"q":"xxx"}
Conclusion
The fact that router.query
is not populated on the client (for SSG pages) on the first run is a design implementation detail. And you can monitor whether it's has been populated or not by checking the router.isReady
boolean property.
Upvotes: 24
Reputation: 563
This solution worked for me:
const router = useRouter();
React.useEffect(() => {
if (router.isReady) {
// Code using query
console.log(router.query);
}
}, [router.isReady]);
Upvotes: 16
Reputation: 693
Building on @cbdeveloper answer, here is example code running NextJS for getting the router data during client-side rendering:
filename: /pages/[...results].tsx
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
export default function Results() {
const router = useRouter();
const { q } = router.query;
const [searchQuery, setSearchQuery] = useState("");
useEffect(() => {
console.log(`useEffect triggered`);
router.isReady ? setSearchQuery(q as string) : console.log('router is not ready');
},[q]);
return (
<>
<p>q: {router.query.q}</p>
<p>searchQuery: {searchQuery}</p>
<p>bar: {router.query.bar}</p>
<form>
<label>Search Query:</label>
<input
type="text"
name="q"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
<button type="submit">Submit</button>
</form>
</>
);
}
Upvotes: 3
Reputation: 39
That's because nextjs works on both server side and client side and it becomes necessary to check if you are actually on the server or client by doing something like typeof window !== 'undefined'
because the component needs to be hydrated on the client side.
When you use the useRouter
hook it runs only on the client side for obvious reasons, but when nextjs is delivering the "component" to the browser it has no idea what router.query.q
refers to, it's only when the component gets hydrated on the client side the react hook kicks in and you can then retrieve the queryString
It is also similar to the reason why you would need to use dynamic
importing in
your nextjs app for most client side libraries.
P.S. I'm new nextjs myself, but I hope this made some sense.
Upvotes: 2