Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Page state is not reset for navigating between dynamic routes #9992

Closed
ZigZagT opened this issue Jan 8, 2020 · 52 comments
Closed

Page state is not reset for navigating between dynamic routes #9992

ZigZagT opened this issue Jan 8, 2020 · 52 comments
Milestone

Comments

@ZigZagT
Copy link

ZigZagT commented Jan 8, 2020

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

@Janpot
Copy link
Contributor

Janpot commented Jan 8, 2020

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.

@Shaker-Hamdi
Copy link

@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.

@samuelgoldenbaum
Copy link

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
    }
}

@pom421
Copy link

pom421 commented May 13, 2020

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()) }
}

@tmikeschu
Copy link

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.

@ishan123456789
Copy link

Any solution?

@tomgreeEn
Copy link

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 ?

@Timer Timer modified the milestones: 9.x.x, backlog Jun 15, 2020
@JWeis
Copy link

JWeis commented Jun 27, 2020

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.

@danilockthar
Copy link

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

@tomgreeEn
Copy link

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).

@danilockthar
Copy link

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
}
}

@zyzski
Copy link

zyzski commented Jul 9, 2020

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

@modulizer-lee
Copy link

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

@tmikeschu

This comment has been minimized.

@modulizer-lee
Copy link

modulizer-lee commented Jul 30, 2020

@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.

@tmikeschu
Copy link

@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.

@valse
Copy link
Contributor

valse commented Aug 5, 2020

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

@vihapr
Copy link

vihapr commented Oct 31, 2020

Hello, any workaround for this issue with getStaticProps ?

@vladfulgeanu
Copy link

vladfulgeanu commented Nov 16, 2020

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.

@sirdeggen
Copy link

sirdeggen commented Nov 22, 2020

I have just come across a solution in my case.

My eg. is involving a link from one page/[id1]/something.js to some other page/[id2]/something.js

The solution for me was:

export default function SomePage() {
    const [something, setSomething] = useState(undefined)
    const router = useRouter()
    const { id } = router.query
    useEffect(() => {
        if (id) setSomething(undefined) // remove the old page data every time the id chages
    }, [id])
 
   const { data, error } = useSWR( id ? '/api/get/' + id : null, fetch)

   useEffect(() => {
      if(data) setSomething(data) // the new Page data
    }, [data])

    return (<div>{something?.whatever}</div>

}

tzengerink pushed a commit to tzengerink/annekelabordus.nl that referenced this issue Dec 1, 2020
Due to an issue in NextJS the state of a page might not be properly
reset when navigating. Through the use of an event listener this can be
mitigated.

See: vercel/next.js#9992
@MaciejWiatr
Copy link

Im still having This issue when using getServerSideProps, any updates?

@kelvinlouis
Copy link

For me the fix with providing a key prop only works if the user navigated to the page using the Link component. If the user navigates back by using the browser history getServerSideProps does not get triggered.

@samuelgoddard
Copy link

Experiencing this issue on 10.0.1 - the key: fix doesn't work for me either :( does anyone have any suggestions?

@mattrosno
Copy link

mattrosno commented Apr 3, 2021

Also experiencing issues with getStaticProps and browser back, even after adding a unique key to the props return.

This feels so wrong but it's working and I can't find a better solution.

Using next version 10.0.4.

export default function Page({
  data,
}) {
  const router = useRouter();
  const uid = useRef(router.query.uid);

  /**
   * If for some reason this component has stale props from navigating back by
   * browser history, reload the entire page.
   *
   * @see {@link https://github.com/vercel/next.js/issues/9992}
   */
  useEffect(() => {
    if (router.query.uid != uid.current) {
      router.reload();
    }
  }, [router.query.uid]);

  return <div>...</div>;
}

export const getStaticProps = async ({ params, previewData = {} }) => {
  const data = await queryByUid('product', params.uid, {
    ...previewData,
  });

  return {
    props: {
      data,
      key: params.uid,
    },
    revalidate: 60,
  };
};

export const getStaticPaths = async () => {
  const { results } = await queryByType('product');

  const paths = results.map((result) => ({
    params: {
      uid: result.uid,
    },
  }));

  return {
    paths,
    fallback: false,
  };
};

@valse
Copy link
Contributor

valse commented Apr 3, 2021

The working solution is that from @samuelgoddard adding the router.asPath key to the _app page Component:
<Component {...pageProps} key={router.asPath} />

@MANTENN
Copy link

MANTENN commented Apr 9, 2021

@mattrosno Sounds like you have a custom _app. Returning props would work without a custom app so if you are using a custom _app then make sure the Component is receiving the props passed through from the custom _app.

@fcFn
Copy link

fcFn commented Apr 19, 2021

Is this described somewhere in the docs? I really think it should be in the Data Fetching section. Perhaps as another note to each of the data fetching methods (seeing that some of them are already duplicated, e.g. the note about fetch()).

@justincy
Copy link
Contributor

I had the same issue but I only need two stateful components to fully reset so I just added key={router.asPath} to those two components instead of the whole page.

@valclark1
Copy link

valclark1 commented Jul 1, 2021

This is still happening on 11.0.2-canary.3

When I navigate to a detail page with accepts [id].ts and navigate back to the top level page using breadcrumbs(link or router) a request is made to the previous route with stale props.

I'm using getServerSideProps rendering. This method <Component {...pageProps} key={router.asPath} /> does not work.

@Nel-sokchhunly
Copy link

For my issue, I want the useEffect() to trigger when the params of the dynamic route is change. The solution to this is setting the params as the key to useEffect() hook.
useEffect(() => { // execute code }, [params.id]);
In my case, I use my params id

@devuxer
Copy link
Contributor

devuxer commented Jul 2, 2021

Custom _app.tsx with <Component {...pageProps} key={router.asPath} /> definitely solved my issue. Wish I had landed on this issue before killing so many hours on this.

As @valse point out, why isn't this the default? @Timer, could this be considered?

@valclark1
Copy link

From the looks of it when using getServerSideProps key is removed from the props. Still can't find a solution for this. May have to reconsider using Next.js because of this issue.

@aldabil21
Copy link

From the looks of it when using getServerSideProps key is removed from the props. Still can't find a solution for this. May have to reconsider using Next.js because of this issue.

In my case the key in getServerSideProps works.

@brunobraga95
Copy link

brunobraga95 commented Aug 12, 2021

In my case I am using link to go from page / to /some_id. When trying to go back to / using the browser arrow the page will not reload again, simply nothing will change.

I added console.logs to the components that should be rendered (NewsCard) and all the logs can be found, which is ever weirder. If they were not even having their render() method called it would make a bit more sense.

I tried the key param, using useEffect with [route.query.id], and passing a key to the at app.js but nothing worked. Any tips?

import Landing from "@containers/landing/Landing";
import { readTranscripts } from "@firebaseDb/index";

function LandingPage(props) {
  console.log(props);
  return <Landing {...props} />;
}

export async function getStaticProps(context) {
  const newsTranscripts = await readTranscripts();
  const transcriptsWithoutFirebaseTimestamp = newsTranscripts.map(
    transcript => ({ ...transcript, createdAt: transcript.createdAt.toDate().toJSON() })
  );
  return {
    props: {
      news: transcriptsWithoutFirebaseTimestamp,
      key: Number(new Date())
    } // will be passed to the page component as props
  };
}

export default LandingPage;

const Landing = ({ news }) => {
  return (
    <Wrapper>
      {news.map(newItem => (
        <React.Fragment>
          <Link href={"/news/" + newItem.title}>
            <a>
              <NewsCard new={newItem} />
            </a>
          </Link>

          <StyledDivider light />
        </React.Fragment>
      ))}
    </Wrapper>
  );
};

@aldabil21
Copy link

In my case I am using link to go from page / to /some_id. When trying to go back to / using the browser arrow the page will not reload again, simply nothing will change.

I added console.logs to the components that should be rendered (NewsCard) and all the logs can be found, which is ever weirder. If they were not even having their render() method called it would make a bit more sense.

I tried the key param, using useEffect with [route.query.id], and passing a key to the at app.js but nothing worked. Any tips?

import Landing from "@containers/landing/Landing";
import { readTranscripts } from "@firebaseDb/index";

function LandingPage(props) {
  console.log(props);
  return <Landing {...props} />;
}

export async function getStaticProps(context) {
  const newsTranscripts = await readTranscripts();
  const transcriptsWithoutFirebaseTimestamp = newsTranscripts.map(
    transcript => ({ ...transcript, createdAt: transcript.createdAt.toDate().toJSON() })
  );
  return {
    props: {
      news: transcriptsWithoutFirebaseTimestamp,
      key: Number(new Date())
    } // will be passed to the page component as props
  };
}

export default LandingPage;

const Landing = ({ news }) => {
  return (
    <Wrapper>
      {news.map(newItem => (
        <React.Fragment>
          <Link href={"/news/" + newItem.title}>
            <a>
              <NewsCard new={newItem} />
            </a>
          </Link>

          <StyledDivider light />
        </React.Fragment>
      ))}
    </Wrapper>
  );
};

I think this is different issue than the OP. Cuz / and /[some_id] are supposed to be 2 different pages. The OP here is talking about the case which when navigating between /[some_id] and /[some_other_id] page would not reset the state, which actually has nothing to do with Next it's just how React works and re-render elements.

However, in the code you've posted, this supposed to be the / page and not the /[some_id] page, cuz I don't see any use of the some_id param nor having a getStaticPaths method which is required when using a dynamic route with getStaticProps. So its a statically generated page without a state, there is nothing to be resetted here.

I tried the key param, using useEffect with [route.query.id], and passing a key to the at app.js but nothing worked.

Did you mean _app.js? As far as I know this is not how you get access to the dynamic route params, you access it using the context provided by one of Nextjs data fetching methods.

Can you show you pages folder structure and your /[some_id] page content. Did you fully read and took a good look at Data fetching and Dynamic Routes sections?

@Brenndoerfer
Copy link

Brenndoerfer commented Aug 29, 2021

In Next 11 it's still not properly reloading/resetting components state when navigating from one dynamic page e.g. /[slug] (/a -> /b) to another.

Is there any known workaround for this?

@rverton
Copy link

rverton commented Aug 29, 2021

In Next 11 it's still not properly reloading/resetting components state when navigating from one dynamic page e.g. /[slug] (/a -> /b) to another.

Is there any known workaround for this?

As a workaround, the one referenced by @devuxer here worked for me.

@Deep-Codes
Copy link

Fixed with some help from the Discord community - the issue for me was the key sent to my <Component> in _app.jswas wrong, i was previously using:

<Component {...pageProps} key={router.route} />

updating to:

<Component {...pageProps} key={router.asPath} />

fixed it for me

This works thanks a ton ❤️

@bhatvikrant
Copy link

Fixed with some help from the Discord community - the issue for me was the key sent to my <Component> in _app.jswas wrong, i was previously using:

<Component {...pageProps} key={router.route} />

updating to:

<Component {...pageProps} key={router.asPath} />

fixed it for me

Perfect! this works!

@Pranav2612000
Copy link

Fixed with some help from the Discord community - the issue for me was the key sent to my <Component> in _app.jswas wrong, i was previously using:

<Component {...pageProps} key={router.route} />

updating to:

<Component {...pageProps} key={router.asPath} />

fixed it for me

This worked for me as well. Thank you. To add, just for completeness, the router is the value defined using useRouter

import { useRouter } from "next/router";
const router = useRouter();    /* Inside the component */

@ijjk
Copy link
Member

ijjk commented Sep 30, 2021

Hi, I'm gonna close this as a duplicate of #26270. We have also added a note for this behavior in the documentation here https://nextjs.org/docs/api-reference/next/router#usage

@ijjk ijjk closed this as completed Sep 30, 2021
@NikitaMazur
Copy link

I tried all the solutions from this topic and not one of them helped me solve the problem with the getStaticProps + browser back button. Only these lines helped to solve the problem. I know it doesn't look very good, but it saved my working day :)
useEffect(() => { window.addEventListener('popstate', function() { location.reload() }, false) }, [])

@santsys
Copy link

santsys commented Oct 22, 2021

I've been having this same issue and been able to mostly solve it (in many places) using react key properties on my markup that contain the dynamic route value... For example...

For a route that uses a [lang] property to change language... e.g. /[lang]/page1

<Page key={'page_' + props.lang}>...</Page>

Setting the unique key based on the property seems to let react know to re-render the items... it seems even if the data of the page is different, when there is the same fundamental component, react doesn't know to properly update the components on the page. Figured I'd share what seems to be working for me in a lot of cases.

@balazsorban44
Copy link
Member

This issue has been automatically locked due to no recent activity. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@vercel vercel locked as resolved and limited conversation to collaborators Jan 27, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests