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

Docs: "manually ensure the state is updated using useEffect" is vague and hidden #35138

Closed
jameshfisher opened this issue Mar 8, 2022 · 8 comments · Fixed by #35651
Closed
Labels
good first issue Easy to fix issues, good for newcomers

Comments

@jameshfisher
Copy link
Contributor

What is the improvement or update you wish to see?

In the docs for next/router I read:

Note: When navigating to the same page in Next.js, the page's state will not be reset by default, as the top-level React component is the same. You can manually ensure the state is updated using useEffect.

I think there are two problems here:

  1. The advice "ensure the state is updated using useEffect" is too vague. Where should I put this useEffect? What should it do, and when? Is there an example anywhere?
  2. This should be less of a buried "Note", and more of a prominent "Warning". I spent two hours tracking down this mysterious bug. I only stumbled onto the "Note" while searching for a bug report for this behavior.

Is there any context that might help us understand?

Here is a minimal test case that I expected to work:

// pages/[pageTitle].jsx

import Link from "next/link";
import * as React from "react";

export const getServerSideProps = async ({ params }) => ({
  props: {
    pageTitle: params["pageTitle"]
  }
});

const MyPage = ({ pageTitle }) => {
  const [title] = React.useState(pageTitle);
  return (
    <div>
      <h1>{title}</h1>
      <Link href="/one">
        <a>one</a>
      </Link>{" "}
      <Link href="/two">
        <a>two</a>
      </Link>
    </div>
  );
};

export default MyPage;

I expected that navigating between /one and two would refresh the state. However, it does not, and so the page title remains the title of the initially loaded page.

My expectation came from the assuming that the identity (or React key) of the current component is the current URL. In other words, I expected the default app.tsx to look something like this:

import { AppProps } from "next/app";
import { useRouter } from "next/router";

function MyApp({ Component, pageProps }: AppProps) {
  const router = useRouter();
  return <Component key={router.asPath} {...pageProps} />;
}

export default MyApp;

In fact, using the above as my _app.ts seems to fix the issue. What problems are there with this approach?

Does the docs page already exist? Please link to it.

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

@jameshfisher jameshfisher added the Documentation Related to Next.js' official documentation. label Mar 8, 2022
@jameshfisher jameshfisher changed the title Docs: Docs: "manually ensure the state is updated using useEffect" is vague and hidden Mar 8, 2022
@jameshfisher
Copy link
Contributor Author

The current docs were written by @ijjk in #26320. (Before then, there was nothing documenting this behavior?!)

@ijjk could you expand a bit on what you meant by "ensure the state is updated using useEffect", maybe with a minimal example? I'm currently working around the issue using React keys, which feels more idiomatic to me -- are there problems with using keys that I haven't considered?

Also: are there ways we could avoid other devs going on multi-hour debugging sessions, only to eventually find this note after debugging? E.g.

@jameshfisher
Copy link
Contributor Author

I also think the documentation should explain why the current behavior is as it is -- what's the advantage of it? Can I do anything cool with the current behavior, that makes up for its surprisingness? Currently the note in the docs reads like a bug description.

@balazsorban44
Copy link
Member

balazsorban44 commented Mar 8, 2022

This seems like a React confusion though, not Next.js specific.

What problems are there with this approach?

When you navigate between pages, the page component receives the new props (pageTitle in your case). Passing an argument to useState is telling React to initialize the state with that value, but you never update it using setTitle. See: https://reactjs.org/docs/hooks-reference.html#usestate

Adding this would work:

useEffect(() => {
  setTitle(pageTitle)
}, [pageTitle])

but it's unnecessary in your example's case. If you rely on the updated pageTitle value during render, just use the pageTitle value directly.

I believe our docs is clear enough around this, but let me know how would you change it based on the above. 👍

Maybe https://nextjs.org/learn/basics/navigate-between-pages/client-side could indeed have a note around this.

@jameshfisher
Copy link
Contributor Author

Note that my example above is artificially minimal. My real application is much larger, with pages using a Zustand react context, which is initialized using Next.js props. I was very surprised to find that the Zustand context was not re-initialized when navigating to a new page. In the example above, I replaced all this with just a useState so as to not carry over the Zustand baggage into this issue.

I'm not sure that using useEffect to reset state is a good general solution. For example, I'm not sure how/where I'd do that with this Zustand context. It also feels easy to forget to add a useEffect for every piece of state. Using a React key feels cleaner, since React keys are specifically made to define distinct identities that should not share state. (I couldn't find this in official React docs, but here's what I mean.)

@balazsorban44 balazsorban44 added area: documentation good first issue Easy to fix issues, good for newcomers and removed Documentation Related to Next.js' official documentation. labels Mar 10, 2022
@balazsorban44
Copy link
Member

balazsorban44 commented Mar 10, 2022

Let's expand that note to also show a code snippet. 👍

Do you have a good suggestion?

@keshav-bohr
Copy link

Hey. Just an add-on. I was just going through the issues.

I agree with @balazsorban44
React hooks are specific to the fact that you re-render only when any changes happen. And you change the data only using the hooks methods. If the react was made according to your assumptions, think about all the unnecessary changes that would occur even if they are not required. So you change the data, only which is needed to change (using useEffect). Otherwise it will just re-render with the previous data.

That is the whole point of hooks i guess. I might be wrong in understanding your point.

jameshfisher added a commit to jameshfisher/next.js that referenced this issue Mar 28, 2022
@jameshfisher
Copy link
Contributor Author

I've opened a PR that explains the issue and shows ways to work around it. I still think this is an important enough conceptual issue that it should be in the tutorial, but let's start with the docs.

ijjk added a commit that referenced this issue Apr 16, 2022
* Docs: workarounds for router not resetting state (fixes #35138)

* Apply suggestions from code review

* updates

Co-authored-by: JJ Kasper <jj@jjsweb.site>
@github-actions
Copy link
Contributor

This closed issue has been automatically locked because it had no new activity for a month. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 16, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
good first issue Easy to fix issues, good for newcomers
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants