From 1497ab3a5864b9fd951497ec2e077fa4c8e10b73 Mon Sep 17 00:00:00 2001 From: Jacob Ebey Date: Mon, 8 Aug 2022 14:56:01 -0700 Subject: [PATCH] chore: add subscribe method to transition manager (#3964) chore: update RemixEntry to use new subscribe method --- .changeset/slow-apples-pretend.md | 5 +++ .../remix-react/__tests__/transition-test.tsx | 4 +-- packages/remix-react/components.tsx | 34 ++++++++++++------- packages/remix-react/transition.ts | 19 +++++++++-- 4 files changed, 45 insertions(+), 17 deletions(-) create mode 100644 .changeset/slow-apples-pretend.md diff --git a/.changeset/slow-apples-pretend.md b/.changeset/slow-apples-pretend.md new file mode 100644 index 00000000000..7547330d377 --- /dev/null +++ b/.changeset/slow-apples-pretend.md @@ -0,0 +1,5 @@ +--- +"@remix-run/react": patch +--- + +Add subscribe method to transition manager to allow subscribing and un-subscribing for React 18 strict mode compliance. diff --git a/packages/remix-react/__tests__/transition-test.tsx b/packages/remix-react/__tests__/transition-test.tsx index c4a37e78b3a..a0b87ed0e3c 100644 --- a/packages/remix-react/__tests__/transition-test.tsx +++ b/packages/remix-react/__tests__/transition-test.tsx @@ -28,7 +28,6 @@ describe("init", () => { actionData: { root: "ACTION DATA" }, error: new Error("lol"), errorBoundaryId: "root", - onChange: () => {}, onRedirect: () => {}, }); expect(tm.getState()).toMatchInlineSnapshot(` @@ -1956,7 +1955,6 @@ function createTestTransitionManager( loaderData: { root: "ROOT" }, location, routes: [], - onChange() {}, onRedirect() {}, ...init, }); @@ -2090,11 +2088,11 @@ let setup = ({ url } = { url: "/" }) => { ]; let tm = createTestTransitionManager(url, { - onChange: handleChange, onRedirect: handleRedirect, loaderData: { root: "ROOT" }, routes, }); + tm.subscribe(handleChange); let navigate_ = ( location: Location | string, diff --git a/packages/remix-react/components.tsx b/packages/remix-react/components.tsx index 3a6bef315da..4627a44e030 100644 --- a/packages/remix-react/components.tsx +++ b/packages/remix-react/components.tsx @@ -49,7 +49,12 @@ import type { RouteMatch as BaseRouteMatch } from "./routeMatching"; import { matchClientRoutes } from "./routeMatching"; import type { RouteModules, HtmlMetaDescriptor } from "./routeModules"; import { createTransitionManager } from "./transition"; -import type { Transition, Fetcher, Submission } from "./transition"; +import type { + Transition, + TransitionManagerState, + Fetcher, + Submission, +} from "./transition"; //////////////////////////////////////////////////////////////////////////////// // RemixEntry @@ -117,20 +122,25 @@ export function RemixEntry({ catch: entryComponentDidCatchEmulator.catch, catchBoundaryId: entryComponentDidCatchEmulator.catchBoundaryRouteId, onRedirect: _navigator.replace, - onChange: (state) => { - setClientState({ - catch: state.catch, - error: state.error, - catchBoundaryRouteId: state.catchBoundaryId, - loaderBoundaryRouteId: state.errorBoundaryId, - renderBoundaryRouteId: null, - trackBoundaries: false, - trackCatchBoundaries: false, - }); - }, }); }); + React.useEffect(() => { + let subscriber = (state: TransitionManagerState) => { + setClientState({ + catch: state.catch, + error: state.error, + catchBoundaryRouteId: state.catchBoundaryId, + loaderBoundaryRouteId: state.errorBoundaryId, + renderBoundaryRouteId: null, + trackBoundaries: false, + trackCatchBoundaries: false, + }); + }; + + return transitionManager.subscribe(subscriber); + }, [transitionManager]); + // Ensures pushes interrupting pending navigations use replace // TODO: Move this to React Router let navigator: Navigator = React.useMemo(() => { diff --git a/packages/remix-react/transition.ts b/packages/remix-react/transition.ts index 346fd5d7637..0249db94826 100644 --- a/packages/remix-react/transition.ts +++ b/packages/remix-react/transition.ts @@ -97,10 +97,13 @@ export interface TransitionManagerInit { error?: Error; catchBoundaryId?: null | string; errorBoundaryId?: null | string; - onChange: (state: TransitionManagerState) => void; onRedirect: (to: string, state?: any) => void; } +export type TransitionManagerSubscriber = ( + state: TransitionManagerState +) => void; + export interface Submission { action: string; method: string; @@ -422,6 +425,7 @@ export function createTransitionManager(init: TransitionManagerInit) { let navigationLoadId = -1; let fetchReloadIds = new Map(); let fetchRedirectIds = new Set(); + let subscribers = new Set(); let matches = matchClientRoutes(routes, init.location); @@ -462,7 +466,10 @@ export function createTransitionManager(init: TransitionManagerInit) { } state = Object.assign({}, state, updates); - init.onChange(state); + + for (let subscriber of subscribers.values()) { + subscriber(state); + } } function getState(): TransitionManagerState { @@ -1382,7 +1389,15 @@ export function createTransitionManager(init: TransitionManagerInit) { markFetchersDone(doneKeys); } + function subscribe(subscriber: TransitionManagerSubscriber) { + subscribers.add(subscriber); + return () => { + subscribers.delete(subscriber); + }; + } + return { + subscribe, send, getState, getFetcher,