Skip to content

Bug: [StrictMode] getSnapshot is not called after subscribing to external store when used in Suspense #35550

@jzhan-canva

Description

@jzhan-canva

in useSyncExternalStore, subscribe is called as a passive effect, so it's possible the store value has changed between render and effect. As a solution, I can see react calls getSnapshot right after subscribing to store, to check if value is stale.

However, when using Suspense in StrictMode, getSnapshot is not called after subscribing to store

React version: 19.2.3

Steps To Reproduce

Consider below code (codesandbox)

const subscribe = (onStoreChange: () => void) => {
  console.log("subscribe called");
  return () => {
    console.log("unsubscribe called");
  };
};
const getSnapshost = () => {
  console.log("getSnapshot called");
  return 1;
};

const Content = ({ promise }: { promise: Promise<any> }) => {
  React.use(promise);
  const value = React.useSyncExternalStore(subscribe, getSnapshost);
  console.log("rendered with value", value);
  return <div>Value {value}</div>;
};

export default function App() {
  const [, reRender] = React.useReducer((state: number) => state + 1, 0);
  const promise = React.useRef(Promise.resolve());
  return (
    <div>
      <button
        onClick={() => {
          console.log("button clicked");
          promise.current = new Promise<void>((resolve) => {
            setTimeout(() => {
              resolve();
            }, 500);
          });
          reRender();
        }}
      >
        Trigger Suspense
      </button>

      <Suspense fallback={<p>Loading...</p>}>
        <Content promise={promise.current} />
      </Suspense>
    </div>
  );
}

The current behavior

In StrictMode, after I click the button to trigger suspense, when it's resolved, it will re-mount the Content component, unsubscribe store, subscribe store, without calling getSnapshot again
my console is

button clicked
getSnapshot called
getSnapshot called
rendered with value 1
getSnapshot called
getSnapshot called
rendered with value 1
unsubscribe called
subscribe called
Image

The expected behavior

The expected behavior is that getSnapshot should be always called after subscribe is called, i.e. in console I should see

subscribe called
getSnapshot called <-- after last subscribe

Metadata

Metadata

Assignees

No one assigned

    Labels

    Status: UnconfirmedA potential issue that we haven't yet confirmed as a bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions