16

I've got a page on my Next.js app that does the following:

  • It's a search page
  • The pathname is like: /search?q=search+slug
  • It loads data on the client
  • It needs to read the router.query to get the router.query.q value

PS: 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:

enter image description here

And then:

enter image description here

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?

4 Answers 4

24

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 of useEffect 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.

Sign up to request clarification or add additional context in comments.

1 Comment

Can you elaborate this in class component ?
16

This solution worked for me:

const router = useRouter();
React.useEffect(() => {
  if (router.isReady) {
    // Code using query
    console.log(router.query);
   }
}, [router.isReady]);

1 Comment

Can you elaborate this in class component? Also what If I try to pass the query to another component ?
3

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>
    </>
  );
}

3 Comments

I ended up in this question because I've been trying to debug routing using catch all routes (pages/[...slug].js). When navigating from pages/index.js to a [...slug].js page everything works fine. But when I try to navigate from a [...slug].js to another [...slug].js page (e.g. /careers to /blog) only the URL changes, but no rehydration or rerendering happen, do you have any possible solution?
This above example does not work for me using next 12.1.0. For some reason the useEffect hook gets triggered only once saying "router is not ready" and that's it.
router.isReady should be in deps of useEffect ([q, router.isReady])
2

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.

2 Comments

Thanks. Are you sure this is the reason? It kind of makes sense, but it's also weird why you'd need an extra run for the useRouter hook. Why wouldn't you make that window check on the 1st run right away?
I'm investigating it further. I think your answer is on the right track. It seems that Next.js initially gets the query object from window.__NEXT_DATA__, which is the prerendered data from the server. And if you didn't populate that on the server (probably by using a dynamic route or using getServerProps), the router.query obj will be empty on the first render. It will only read the query from your browser in a later render cycle. I don't know exactly when it happens, but it seems to happen automatically. I'm also asking it here.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.