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

Regression: usePrevious prematurely updates in StrictMode — needs to useEffect #1315

Closed
dcporter opened this issue Jun 24, 2020 · 10 comments
Closed
Labels

Comments

@dcporter
Copy link

dcporter commented Jun 24, 2020

What is the current behavior?

As of #1295 (v15.3.0), if React renders a component twice before committing (as it does to every component in the excellent <React.StrictMode>, and apparently as CodePen does even without StrictMode), the second render will get a prematurely updated value. The React docs (which the react-use docs references) uses useEffect for this reason. That PR should be reverted. 😞

Steps to reproduce it and if possible a minimal demo of the problem. Your bug will get fixed much faster if we can run your code and it doesn't have extra dependencies other than react-use. Paste the link to your JSFiddle or CodeSandbox example below:

Use <StrictMode> to turn on double-rendering. Use usePrevious. Note that on the second render, the usePrevious value is from the first (duplicate, discarded) render, not from the previous commit. This means that, for the render which ends up getting committed, the usePrevious value that ends up reflected in the DOM is always the current one, not the previous one.

What is the expected behavior?

The previous value should not update until after the render is committed, via useEffect.

A little about versions:

@xobotyi
Copy link
Contributor

xobotyi commented Jun 24, 2020

I'm a bit confused, why the hell It does rendering twice 😱 to suck laptops batteries twice faster?🤔

@dcporter
Copy link
Author

dcporter commented Jun 24, 2020

Render functions should be cheap and must not have side effects. StrictMode double-renders to enforce both. Updating the "previous value" counts as a side effect — it's not idempotent, if you call it twice with the same inputs you get different outputs.

From the docs:

Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:

@xobotyi
Copy link
Contributor

xobotyi commented Jun 24, 2020

Well, since you can use hooks only in function components which is renderer as-is, using usePrevious breaks the purity rule by the fact of usage)
Anyways I'll revert PR as soon I get to my desktop.

@dcporter
Copy link
Author

Anyways I'll revert PR as soon I get to my desktop.

<3 thank you much!

Well, since you can use hooks only in function components which is renderer as-is, using usePrevious breaks the purity rule by the fact of usage)

This isn't quite right, if you're curious. (Unless I'm misunderstanding you.) useEffect is itself a pure function, because it doesn't execute the function — in a weird round-about way it adds to the function's return value (JSX + closure-matching effect function). It doesn't invoke that function, it purely provides React with the information it needs to maybe invoke it later. useRef is a pure function too — it kind of adds to the arguments, but it's got a foot-gun because it gives it to you in a way that lets you change that value impurely. (I'm currently debating at work about whether it's ever okay to update refs from render.)

This has been very helpful and enjoyable for me to work through that in my head and write out, thank you for your forbearance. 😄

@xobotyi
Copy link
Contributor

xobotyi commented Jun 24, 2020

@dcporter AFAIK in general useEffect simply (not simply at all 🤪) invokes provided function if any of elements of deps array has changed so it is not a pure function.
We're closing our eyes in two points here and lying to ourselves that SFC using hooks is pure (its not):

  1. Providig the 'effector' to useEffect in our SFC is like we saying "oh its not me, its is hook, i gave him a function, 'magic' happened and it was invoked, its not me, im pure!"
  2. And from the point of useEffect it is like "GTFO I'm idempotent, until you giving me the same arguments i'll do the same, im not even invoking it by myself, i give it to other guy, im pure!", but its not, received function can di anything and from the point of useEffect it is nothing else than side effect since hook cant guaratntee the presistence of what the function doing.

@dcporter
Copy link
Author

Like you said, useEffect doesn't invoke the function itself, it registers it to be called at some point in the future. (And it does so in an idempotent way — we trust React to match effects up with rendered output, and either use them or dump them on the floor together.) Consider the following alternate API for side effects:

function MyComponent() {
    return {
        jsx: <h1>It's Pure!</h1>,
        effects: [{ effector: () => console.log("side effect."), dependencies: [] }]
    };
}

That's an entirely pure function—totally idempotent, no side effects. You can call it as many times as you want (again, React reserves the right to do this — Suspense being the main reason). useEffect is doing the same thing behind the scenes.

(not simply at all 🤪)

😂 cheers m8

@dcporter
Copy link
Author

Hi Anton, any update on this? Thanks much either way!

@xobotyi
Copy link
Contributor

xobotyi commented Jun 29, 2020

@dcporter i've forgot to push the changes 🤣😅
Reverted changes on friday, but forgot to push 🤦‍♂️

streamich pushed a commit that referenced this issue Jun 29, 2020
## [15.3.1](v15.3.0...v15.3.1) (2020-06-29)

### Bug Fixes

* **usePrevious:** revert the reworked variant as a fix of [#1315](#1315) ([a4279eb](a4279eb))
@xobotyi xobotyi closed this as completed Jun 29, 2020
@streamich
Copy link
Owner

🎉 This issue has been resolved in version 15.3.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

@dcporter
Copy link
Author

Huge, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants