Next.js: Page state is not reset for navigating between dynamic routes

Created on 8 Jan 2020  路  19Comments  路  Source: vercel/next.js

Bug report

Describe the bug

The page state is not reset for navigation between dynamic routes that served by the same source component.

for example, give page source /a/[param]/index.js, when navigating from /a/1 to /a/b, states on the page won't' be reset.

The causing is that for this kind of navigation, what actually happened is the same React Component been rendered with different props. Thus react takes it as a component is rerendering itself, and causing the new navigated page receive stale states.

To fix it, just add {key: <composed base on URL routing params> } to page initial props.

To Reproduce

I've created a live example:

Edit next-bug-dynamic-route-not-rerender

Expected behavior

Page should be fully reloaded and states should be reset.

Screenshots

N/A

System information

  • OS: ALL
  • Browser: ALL
  • Version of Next.js: 9.1.6

Additional context

story needs investigation

Most helpful comment

Great find, suggest docs are updated. For future devs, same should be applied in getStaticProps()

export async function getStaticProps({params}) {
    const props = await getData(params);

    // key is needed here
    props.key = data.id; 

    return {
        props: props
    }
}

All 19 comments

Yes! I've seen similar confusing behavior when using useEffect.

  React.useEffect(() => {
    console.log("page initial render");
  }, []);

It only fires once, even when you change the page, but stay on the same route. I'm not sure if it's wrong, but it's definitely not intuitive in my opinion. It's non-intuitive because it works differently when changing the page, but coming from a different route.

@BananaWanted I just got this exact issue and thanks to @Janpot he pointed me to this post and tried your solution and it worked for me.

I'm not sure if this is a "next router" issue or a react issue, but even with your workaround/solution sometimes the browser back button doesn't work as expected, so I think this should be fixed.

Great find, suggest docs are updated. For future devs, same should be applied in getStaticProps()

export async function getStaticProps({params}) {
    const props = await getData(params);

    // key is needed here
    props.key = data.id; 

    return {
        props: props
    }
}

Thanks for the example with the key props. Makes me think of a resource I read recently from Kent C Dodds on the topic : https://kentcdodds.com/blog/understanding-reacts-key-prop

For my need, I have made a bespoke key based on the current timestamp, to ensure to always have a new value :

MyPage.getInitialProps = (ctx) => {
    const { id } = ctx.query
    if (!id || isNaN(id)) return { initialUser: {}, key: Number(new Date()) }
}

Confirming that

Page.getInitialProps = (c) => {
  return {
    id: String(c.query.id),
    key: String(c.query.id),
  };
};

Produced the expected mount/unmount effects that @Janpot mentioned.

Any solution?

I have this issue also. The suggested solution (adding a key) works for me in development builds but not in full builds.
Any idea why that could be ?

I also ran into this problem. Adding key to getStaticProps did the trick. I was able to get this solution to build for production with no issues.

Great find, suggest docs are updated. For future devs, same should be applied in getStaticProps()

export async function getStaticProps({params}) {
    const props = await getData(params);

    // key is needed here
    props.key = data.id; 

    return {
        props: props
    }
}

where is data come from ? i dont get it

It's wherever you get data from in your implementation. As I understand any unique string could be used in lieu of data.id (though it's still not actually working for me in production, I need to revisit).

It's wherever you get data from in your implementation. As I understand any unique string could be used in lieu of data.id (though it's still not actually working for me in production, I need to revisit).

i got something like this, can i put key with data instead of 'prop' example of samuel ?

export async function getStaticProps({ params }) {
const {data} = await comercioBySlug(params.slug)
return{
props: {data},
unstable_revalidate: 10
}
}

seems like this may be happening in my app even when the dynamic part is in the middle of a route users/[id]/analytics -> users/[id]/settings

thanks for posting a workaround

export async function getStaticProps({params}) {
    const props = await getData(params);

    // key is needed here
    props.key = data.id; 

    return {
        props: props
    }
}

I'm using Next version 9.4.4 and this solution isn't working for me

@modulizer-lee key needs to be at the root of the object. You may mean to return props.

@tmikeschu sorry, I'm a little confused about what you mean. What I currently have is this:

export default function ProductPage(props) {

  const [something, setSomething] = useState(1)

  return(
    ...
  )
}

export async function getStaticProps({ params }) {
  const slug = params.product
  const props = await client.query({
    query: singleProductQuery,
    variables: { id: slug }
  })

  props.key = props.data.product.slug

  return {
    props: props
  }
}

Does this look wrong?

I figured another solution to this problem would be to simply enclose the contents of the page within a simple layout component and add a key to that, but it only fixes states within contained components and not the state defined outside, like in my example above.

@modulizer-lee pardon me. I hadn't seen the version change and the preference for getStaticProps. I had getInitialProps in my head, in which the return value itself is the component props, without the props key on the returning object.

OMG this keyed solution is magic!
But shouldn't this be the default behavior for dynamic routes?
In my blog post page I had some "refresh" issues navigation client side and I didn't understood why... until now :P

Hello, any workaround for this issue with getStaticProps ?

The problem with using the key prop in getStaticProps is that, while going from one page with dynamic props to another one with different props will work, going back (using window.history.back()) will only show the last page with dynamic props that was reached.

/a/1 ---> /a/2 ---> /a/3 ---> /a/4 ---> /a/5 This will work ok with key in props.
/a/5 <--- /a/5 <--- /a/5 <--- /a/5 <--- /a/5 This is how the pages will render when using history.back() while having key in props.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

formula349 picture formula349  路  3Comments

swrdfish picture swrdfish  路  3Comments

wagerfield picture wagerfield  路  3Comments

YarivGilad picture YarivGilad  路  3Comments

olifante picture olifante  路  3Comments