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

Recoil state updates are out of sync with React component state #1076

Closed
Pinpickle opened this issue Jun 18, 2021 · 14 comments
Closed

Recoil state updates are out of sync with React component state #1076

Pinpickle opened this issue Jun 18, 2021 · 14 comments
Assignees
Labels
enhancement New feature or request

Comments

@Pinpickle
Copy link

Pinpickle commented Jun 18, 2021

Recoil version: 0.3.1
React version: 17.0.2

If a component has both Recoil state and React component state, and both are updated at once, a full render will occur (including effects and committing to the DOM) with the old Recoil state and the new React component state, before re-rendering with the new Recoil state.

This still occurs when:

  • unstable_batchedUpdates is used
  • React 18 alpha is used (even with ReactDOM.createRoot)

Example code

import { atom, useRecoilState } from "recoil";
import React, { useEffect, useState } from "react";

const indexAtom = atom({
  key: "index",
  default: 0
});

export const TestComponent = () => {
  const [stateIndex, setStateIndex] = useState(0);
  const [recoilIndex, setRecoilIndex] = useRecoilState(indexAtom);

  useEffect(() => {
    console.log("=== Effect from render ===");
    console.log("Local state: ", stateIndex);
    console.log("Recoil state: ", recoilIndex);
  });

  return (
    <button
      onClick={() => {
        setRecoilIndex(recoilIndex + 1);
        setStateIndex(stateIndex + 1);
      }}
    >
      Local state: {stateIndex} <br />
      Recoil state: {recoilIndex}
    </button>
  );
};

When clicking the button, you'll see the following being output:

=== Effect from render === 
Local state:  
0
Recoil state:  
0
=== Effect from render === 
Local state:  
1
Recoil state:  
0
=== Effect from render === 
Local state:  
1
Recoil state:  
1

Notice how on the second effect, local state and recoil state are different, despite being set to the same value on the same call.

I would expect the second effect to never occur - if component state and Recoil state are set simultaneously, they should be updated atomically.

Codesandbox demo: https://codesandbox.io/s/sharp-https-3ydwz?file=/src/TestComponent.js

@Hainesy
Copy link

Hainesy commented Jun 18, 2021

I'm seeing the same issue in v0.2.0

@csantos42 csantos42 self-assigned this Jun 21, 2021
@drarmstr
Copy link
Contributor

Currently they are updated incrementally. However, a change is landing that also adds support for React's useTransition() with Recoil state that will allow React and Recoil state to be updated together. Currently undergoing internal testing.

@drarmstr drarmstr added the enhancement New feature or request label Jun 22, 2021
@Pinpickle
Copy link
Author

@drarmstr sounds great! Does this mean that React 18 will be needed to make use of this change? Any idea when this will land (days, weeks, months?)

@drarmstr
Copy link
Contributor

It's already landed in the nightly build, but to test it you'll have to enable the recoil_early_rendering_2021 feature flag in Recoil_gkx.js and have React's experimental useMutableSource available, though React 18 itself is not required.

@Pinpickle
Copy link
Author

Pinpickle commented Jun 25, 2021

I can confirm after editing the bundle manually on the nightly build, it works!

@drarmstr is there a way to set flags in Recoil_gkx.js without rebuilding?

@drarmstr
Copy link
Contributor

I can confirm after editing the bundle manually on the nightly build, it works!

@drarmstr is there a way to set flags in Recoil_gkx.js without rebuilding?

You should be able to find where the gks variable is set in the built files and patch it there. Sorry we don't have a more sophisticated feature flag system for the open source build yet.

@maxsupera
Copy link

Experiencing the same issue here! cant wait for the update :)

@theninthsky
Copy link

Same as above, as we are heavily relying on this feature :)
We might be misusing things, but I find it very convenient to use useState and useRecoilState interchangeably in conjunction with useEffect.

@timiscoding
Copy link

If you can't wait for the fix, you could patch-package 0.4.0 by enabling the feature flag and install react@next and react-dom@next which includes the requisite useMutableSource hook. I get a warning about not using the newer ReactDOM.createRoot API so it'll behave like React 17 which is fine by me as I'm not ready to opt into concurrent mode.

@drarmstr
Copy link
Contributor

Also see #759

@drarmstr
Copy link
Contributor

drarmstr commented Jan 8, 2022

Addressed in main branch for 0.6 release.

@Leykwan132
Copy link

Is this issue solved? I am still facing the similar issue here.

@Leykwan132
Copy link

Leykwan132 commented Aug 26, 2022

  const profileData = useRecoilValue(profileState);
  const setProfileUrl = useSetRecoilState(profileState);
  useEffect(() => {
    setProfileUrl(JSON.parse(window.localStorage.getItem("profileData")));
    console.log(profileData)
  }, []);

I am trying to set the atom to the localStorage value but it seems like it requires a re-render which would potentially crash the application. Any solution to this, thanks!

@jacksonjp
Copy link

jacksonjp commented Nov 15, 2022

const tempFahrenheit = atom({
    key: 'tempFahrenheit',
    default: 32
  });

  const [tempF, setTempF] = useRecoilState(tempFahrenheit);

  const handleClick = () => {
    flushSync(() => {
      setTempF(11);
    });
    flushSync(() => {
      console.log(tempF);
    });
  };

The above displays 32 when onClick handler is called. Tried to opt out of batching with React 18, but still fails getting the latest value.

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

No branches or pull requests

10 participants