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

Changing atom in useAtom returns previous atom's value once before returning the new atom's value #827

Closed
hannojg opened this issue Nov 9, 2021 · 7 comments · Fixed by #841
Assignees
Labels
bug Something isn't working

Comments

@hannojg
Copy link

hannojg commented Nov 9, 2021

Description

When changing the atom used in a useAtom the return value will be once the previous atom value (updating the component etc), before changing to the new atom's value. So it causes two re-renders instead of one.

Reproduction

A full reproduction can be seen here: https://codesandbox.io/s/jotai-useatom-issue-uo0uc?file=/src/App.js

The important code part looks like this:

const atom0 = atom("atom0");
const atom1 = atom("atom1");
const atom2 = atom("atom2");
const atoms = [atom0, atom1, atom2];

export default function App() {
  const [currentAtomIndex, setCurrentAtomIndex] = useState(0);
  const rotateAtoms = () => {
    setCurrentAtomIndex((prev) => (prev + 1) % atoms.length);
  };

  const [atomValue] = useAtom(atoms[currentAtomIndex]);

  useEffect(() => {
    console.log("Atom index:", currentAtomIndex, "Atom value", atomValue);
  }, [currentAtomIndex, atomValue]);

As you can see, while the currentAtomIndex has already been changed, the atom's value on the first "run" isn't.

Screen.Recording.2021-11-09.at.12.49.50.PM.mov
@hannojg hannojg changed the title Changing atom in useAtom does return previous atom value once Changing atom in useAtom does return previous atom value once before returning the new atom's value Nov 9, 2021
@hannojg hannojg changed the title Changing atom in useAtom does return previous atom value once before returning the new atom's value Changing atom in useAtom returns previous atom value once before returning the new atom's value Nov 9, 2021
@hannojg hannojg changed the title Changing atom in useAtom returns previous atom value once before returning the new atom's value Changing atom in useAtom returns previous atom's value once before returning the new atom's value Nov 9, 2021
@hannojg
Copy link
Author

hannojg commented Nov 9, 2021

Interestingly this works correctly in terms of the useEffect
https://codesandbox.io/s/jotai-useatom-issue-with-usestate-atom-ubcfy

Although, if you change from:

  useEffect(() => {
    console.log("Atom index:", index, "Atom value", atomValue);
  }, [atomValue]);

to

console.log("Atom index:", index, "Atom value", atomValue);

you can see that it actually causes two re-renders

(I am not sure whether I am lacking understanding of a fundamental react/jotai concept that explains this behavior)

@dai-shi
Copy link
Member

dai-shi commented Nov 9, 2021

Thanks for reporting.
Double renders is not a problem (expected), but double effects is unexpected.
I thought this test covers such a case, but maybe something is different.

That said, at this point, we are not sure if this is fixable, or how it behaves in React 18.
So, the best for now is to workaround with tuned deps for such behavior.
Sorry for the inconvenience.

Let me label as bug until we understand how this is happening, but this one is not a high priority.

@dai-shi dai-shi added the bug Something isn't working label Nov 9, 2021
@dai-shi
Copy link
Member

dai-shi commented Nov 9, 2021

@Aslemammad Are you interesting in investigating this issue?

@Aslemammad
Copy link
Member

Let's put it in my TODO list!

@Aslemammad
Copy link
Member

Let me write my report here about what I've found.

The updates weren't synced with the effects, so I tried to synchronize them using the #761 solution, and synchronization worked.

Even though the issue wasn't non-synced renders (maybe it's part of the issue), the problem is more about scheduling.

const unsubscribe = store[SUBSCRIBE_ATOM](atom, forceUpdate)
forceUpdate()
return unsubscribe

As you can see, forceUpdate schedules the update (queue.pending), so the next render can consume it using useReducer. This makes the issue expected, but it'd be good to provide a solution (or point out that this is an issue in React itself, which there's not a high chance in this one).

I'd like to hear your thoughts.

@dai-shi
Copy link
Member

dai-shi commented Nov 23, 2021

This is interesting. It feels like a bug in useReducer, because the first click works expected (one commit), but the second click causes dual commits. tested with #841 tests.
I think you can reproduce the weird behavior without jotai.

That said, even if it's a bug in useReducer, we'd want to fix the problem with the current version. I will add a commit to #841 for a workaround.

dai-shi added a commit to Aslemammad/jotai that referenced this issue Nov 23, 2021
@dai-shi dai-shi linked a pull request Nov 23, 2021 that will close this issue
dai-shi added a commit that referenced this issue Nov 24, 2021
* feat: test case

* a workaround for useReducer weird behavior

* fix a test for react18

* fix bail out check

Co-authored-by: daishi <daishi@axlight.com>
@dai-shi
Copy link
Member

dai-shi commented Nov 27, 2021

It feels like a bug in useReducer,

It turns out that there was a misunderstanding of mine. I thought changing the reducer reference results in the re-evaluation, but how it behaves seems correct. So, #841 is not a workaround, but a correct solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants