84

I got a problem with my dynamic route. It look like this

[lang]/abc

I am trying to get query value from [lang] but when I using useRouter/withRouter i got query during 2-3 render of page ( on first i got query.lang = undefined ). its possible to get in 1 render or use any technique ?

enter image description here

mybrave
  • 1,662
  • 3
  • 20
  • 37
Przemek eS
  • 1,224
  • 1
  • 8
  • 21

9 Answers9

165

I found something:

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. https://nextjs.org/docs/api-reference/next/router#router-object

And the code would be like:

const router = useRouter();
useEffect(()=>{
    if(!router.isReady) return;

    // codes using router.query

}, [router.isReady]);
doxylee
  • 1,771
  • 2
  • 8
  • 8
  • 18
    This should be the accepted answer. This is exactly what isReady was introduced for. – Marnix.hoh Mar 17 '21 at 18:36
  • 1
    This worked like a charm compared to other tricky solutions that could have weird side effects! Thanks – Diego Isco Mar 28 '21 at 14:22
  • Just discovered this by console logging the useRouter function. This solves a lot of resources instead of doing SSR! I would highly recommend this of SSR rendering just path params. – Alex Dunlop Nov 18 '21 at 01:24
  • It says "Should only be used inside of useEffect methods", does it mean that we can't use it with componentDidUpdate? I can't make it work in class-based components. – nomadus Apr 21 '22 at 14:53
63

It's impossible to get the query value during the initial render.

Statically optimized pages are hydrated without the route parameters, so the query is an empty object ({}).

Next.js will populate the query after the page has been hydrated.

Next.js 10.0.5 and up

To determine if the route params are ready, you can use router.isReady inside a useEffect hook. For an example, see the answer provided by @doxylee.

Before Next.js 10.0.5

At first render of a dynamic route router.asPath and router.route are equal. Once query object is available, router.asPath reflects it.

You can rely on the query value within a useEffect hook after asPath has been changed.

const router = useRouter();

useEffect(() => {
  if (router.asPath !== router.route) {
    // router.query.lang is defined
  }
}, [router])

GitHub Issue - Add a "ready" to Router returned by "useRouter"

Nikolai Kiselev
  • 6,201
  • 2
  • 27
  • 37
  • 1
    You should be careful with this approach because it's buggy because `asPath` and `route` can be equal, which doesn't reflect the actual readiness of the router. You can rely on this only when your URL has **always** a query. – Mycolaos Dec 12 '20 at 12:07
  • 1
    Relevant documentation about automatic static optimization and the query object: https://nextjs.org/docs/advanced-features/automatic-static-optimization#how-it-works. – juliomalves Jul 31 '22 at 22:34
15

In NextJS 9+, one way to ensure route parameters are immediately available for page components is to get them from the context arg passed to getServerSideProps() and pass to the component as props.

For a page like [id].js,

export function getServerSideProps(context) {
  return {
    props: {params: context.params}
  };
}

export default ({params}) => {
  const {id} = params;
  return <div>You opened page with {id}</div>;
};
Hunter Medney
  • 381
  • 2
  • 10
  • Tried this on next 9.4.4, and I'm getting an error because `context.props` is `undefined`, which can't be serialized to JSON. Was hoping it would contain the query instead. – Matt Huggins Sep 07 '20 at 19:15
  • `context.props` context has not `props`, look better at the example. Your page props will receive what you return as `{ props: {} }` from `getServerSideProps`. – Mycolaos Dec 12 '20 at 12:58
  • Hi @Mycolaos, the answer was actually leveraging `context.params` - not `context.props`. Just reverified the approach on Next 10. Reference: https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering – Hunter Medney Jan 04 '21 at 18:40
  • Hi @HunterMedney, my comment was directed to Matt Huggins (and Eight as he seems to agree with him). You can see that I'm actually suggesting to look better at your example. Sorry for the confusion. – Mycolaos Jan 05 '21 at 12:52
  • Hi @Mycolaos d'oh, my bad - thank you so much for the clarification! – Hunter Medney Jan 05 '21 at 14:08
  • 5
    Use of `getServerSideProps` instructs Next.js to pre-render a page on the server side on each request. Means it isn't statically optimised and cached. Use it only if you're wiling to sacrifice performance. E.g. if you must fetch data before first render. – Nikolai Kiselev Feb 13 '21 at 07:13
  • 1
    I would not recommend this for just checking params this will cause a SSR which vercel recommends to use sparingly. Use the useRouter: isReady boolean – Alex Dunlop Nov 18 '21 at 01:28
9

This is a great question and one that took a few days for me to figure out what the best approach is.

I have personally found three viable solutions to the problem of validating dynamic route path params or even just route path params in general.

The three solutions are

  1. SSR (don't recommend) [Next >= 10]
  2. useRouter
  3. Middleware [Next 12 required]

In my examples a will use a route that requires a reset-token or it should be redirected.

SSR Firstly server side rending with getServerSideProps. Vercel recommends to use SSR as a last resort and I would highly recommend not using SSR when able (time to byte & cost).

We suggest trying Incremental Static Generation or Client-side Fetching and see if they fit your needs. https://vercel.com/blog/nextjs-server-side-rendering-vs-static-generation

But in the case that you do, say there is some server side api validation call you require to validate the query param.

export const getServerSideProps = async (context) => {
  const { token } = context.query;

  if (!token) {
    return {
      redirect: {
        permanent: false,
        destination: "/",
      }
    }
  }

  return {
    props: {}
    // props: { token }
    // You could do this either with useRouter or passing props
  }
}

useRouter Secondly the easiest useRouter. When I first did this I came across the problem when nextjs/react hydrates there will be a point when the query params are null. Luckily useRouter has isReady!

import Router, { useRouter } from "next/router";

const { query, isReady } = useRouter();

useEffect(() => {
  if (!isReady) return;
  if (!query.token) {
    Router.push("/")
  }
}, [isReady])

Middleware now this is my personal favourite as it seperates the functionality in a clean way imo. I found this based of a vercel example. I would highly recommend reading through a bunch of these to find best practices. https://github.com/vercel/examples/

import { NextResponse, NextRequest } from 'next/server'

export async function middleware(req) {
    const { pathname, searchParams } = req.nextUrl

    if (pathname == '/reset-token') {
        const index = searchParams.findIndex(x => x.key === "token")
        // You could also add token validation here.
        if (!index) {
          return NextResponse.redirect('/')
        }
    }
    return NextResponse.next()
}

Here is the repo which has some cool filtering of query parameters. This is a more soft approach instead of hard redirecting. https://github.com/vercel/examples/tree/main/edge-functions/query-params-filter

Nico also has a great answer on this, expect I wouldn't recommend using hooks like in his example, instead use isReady. https://stackoverflow.com/a/58182678/4918639

Alex Dunlop
  • 1,383
  • 15
  • 24
2

For Class Component Lovers

The even better approach is to listen for a dedicated event for this routeChangeComplete using this.props.router.events.on method, inside componentDidMount if you're using class component -

routeChangeComplete = () => {
    // this WILL have valid query data not empty {}
    console.log(this.props.router.query);
};
componentDidMount() {
    this.props.router.events.on("routeChangeComplete", this.routeChangeComplete);
}
componentWillUnmount() {
    this.props.router.events.off("routeChangeComplete", this.routeChangeComplete);
}

Ref: https://nextjs.org/docs/api-reference/next/router#routerevents

routeChangeComplete: Fires when a route changed completely.

Practically when isReady has become true or when router.query object has data.

Shubham P
  • 1,164
  • 4
  • 18
2

For NextJS version - 12.0.8

"If you export a function called getServerSideProps (Server-Side Rendering) from a page, Next.js will pre-render this page on each request using the data returned by getServerSideProps."

=async functions

refference:https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props#getserversideprops

Simply putting that async function on the page notifies NextJS of its presence.During prerendering stage of the component, the query object of the router will be empty.

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.

refference: https://nextjs.org/docs/api-reference/next/router

solution:

import { useRouter } from 'next/router';

const Fn = () =>{

const router = useRouter();
const { param } = router.query;

const fetchData = async () => {
   await fetch();
} 

 useEffect(() => {
   fetchCat();
}, [router.isReady]);

}
1

I resolved my problem that I need it in Hoc component. I wrapped using withRouter(withLocale(Comp)) and create conditional in HOC

export default function withLocale(WrappedPage) {
    const WithLocale = ({ router, ...props }) => {
        const { lang } = router.query;
        if (!lang || !isLocale(lang)) {
            return <Error statusCode={404} />;
        }
        return (
            <LocaleProvider lang={lang}>
                <WrappedPage {...props} />
            </LocaleProvider>
        );
    };
   return WithLocale;
}
Przemek eS
  • 1,224
  • 1
  • 8
  • 21
0

Next.js <= 10.0.5

This is a good work around, I found around from this comment

Add useQuery.ts helper file

// useQuery.js
import { useRouter } from 'next/router';

// Resolves query or returns null
export default function useQuery() {
  const router = useRouter();
  const ready = router.asPath !== router.route;
  if (!ready) return null;
  return router.query;
}

usage

// In your components (instead of useRouter)
const query = useQuery();

useEffect(() => {
  if (!query) {
    return;
  }
  console.log('my query exists!!', query);
}, [query]);
Ben Winding
  • 10,208
  • 4
  • 80
  • 67
0

Class Component | 12/16/2022 | React JS 18.2.0 | Next JS 13.0.6

I got the answer for those who want to use Class Component. This was actually nowhere to be found ! Enjoy !

You will add if(this.props.router.isReady) and include return in the condition in render().

.
.
import { withRouter } from 'next/router';
import { Component } from 'react';

class User extends Component {

    ...
    
    render() { 
        if(this.props.router.isReady){ // Add this condition and include return ()

            // Do anything

            console.log(this.props.router.query) // Log 'query' on first render

            return (
                <div>
                    <SearchBar pid={this.props.router.query.pid} /> // Pass the query params to another component if needed
                </div>
            );
            
        }
    }
}

export default withRouter(User);
Nima
  • 118
  • 9