diff --git a/.changeset/remove-use-sync-external-store.md b/.changeset/remove-use-sync-external-store.md new file mode 100644 index 00000000000..0871ea2b5d7 --- /dev/null +++ b/.changeset/remove-use-sync-external-store.md @@ -0,0 +1,5 @@ +--- +"@remix-run/react": patch +--- + +Update Remix for React Router no longer relying on `useSyncExternalStore` diff --git a/package.json b/package.json index b23c8490133..59799a63321 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,6 @@ "@types/retry": "^0.12.0", "@types/semver": "^7.3.4", "@types/ssri": "^7.1.0", - "@types/use-sync-external-store": "^0.0.3", "@vanilla-extract/css": "^1.1.0", "abort-controller": "^3.0.0", "abortcontroller-polyfill": "^1.7.3", diff --git a/packages/remix-react/browser.tsx b/packages/remix-react/browser.tsx index 5bd38fe7d8e..1542b32282d 100644 --- a/packages/remix-react/browser.tsx +++ b/packages/remix-react/browser.tsx @@ -1,9 +1,7 @@ import type { HydrationState, Router } from "@remix-run/router"; import type { ReactElement } from "react"; import * as React from "react"; -import type { Location } from "react-router-dom"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; -import { useSyncExternalStore } from "use-sync-external-store/shim"; import { RemixContext } from "./components"; import type { EntryContext, FutureConfig } from "./entry"; @@ -123,7 +121,8 @@ if (import.meta && import.meta.hot) { // TODO: Handle race conditions here. Should abort if a new update // comes in while we're waiting for the router to be idle. Object.assign(window.__remixManifest, newManifest); - window.$RefreshRuntime$.performReactRefresh(); + // Ensure RouterProvider setState has flushed before re-rendering + setTimeout(() => window.$RefreshRuntime$.performReactRefresh(), 1); } }); router.revalidate(); @@ -166,16 +165,20 @@ export function RemixBrowser(_props: RemixBrowserProps): ReactElement { }); } + let [location, setLocation] = React.useState(router.state.location); + + React.useEffect(() => { + return router.subscribe((newState) => { + if (newState.location !== location) { + setLocation(newState.location); + } + }); + }, [location]); + // We need to include a wrapper RemixErrorBoundary here in case the root error // boundary also throws and we need to bubble up outside of the router entirely. // Then we need a stateful location here so the user can back-button navigate // out of there - let location: Location = useSyncExternalStore( - router.subscribe, - () => router.state.location, - () => router.state.location - ); - return (