Skip to content

Commit ad9dfab

Browse files
fix(react): resolve deferred loaders after resume
1 parent 47485cd commit ad9dfab

2 files changed

Lines changed: 74 additions & 61 deletions

File tree

integrations/react/src/loader/loaderPlugin.tsx

Lines changed: 57 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,49 @@ export function loaderPlugin<
3333
SyncInspectableDeferred<unknown>
3434
>();
3535

36+
const resolveLoadPathLoaderData = (actions: LoaderPluginActions) => {
37+
actions
38+
.getStack()
39+
.activities.filter(
40+
(activity) => activity.transitionState !== "exit-done",
41+
)
42+
.forEach((activity) => {
43+
const matchActivity = input.config.activities.find(
44+
(candidate) => candidate.name === activity.name,
45+
);
46+
47+
if (!matchActivity?.loader) {
48+
return;
49+
}
50+
51+
const loaderData = (activity.context as any)?.loaderData as
52+
| SyncInspectablePromise<unknown>
53+
| undefined;
54+
const deferred = loaderData
55+
? loadPathDeferreds.get(loaderData)
56+
: undefined;
57+
58+
if (!loaderData || !deferred) {
59+
return;
60+
}
61+
62+
loadPathDeferreds.delete(loaderData);
63+
64+
Promise.allSettled([loaderData]).then(([loaderDataPromiseResult]) => {
65+
printLoaderDataPromiseError({
66+
promiseResult: loaderDataPromiseResult,
67+
activityName: matchActivity.name,
68+
});
69+
});
70+
71+
try {
72+
deferred.resolve(loadData(activity.name, activity.params));
73+
} catch (error) {
74+
deferred.reject(error);
75+
}
76+
});
77+
};
78+
3679
return {
3780
key: "plugin-loader",
3881
overrideInitialEvents({ initialEvents, initialContext, initInfo }) {
@@ -117,48 +160,16 @@ export function loaderPlugin<
117160
return;
118161
}
119162

120-
actions
121-
.getStack()
122-
.activities.filter(
123-
(activity) => activity.transitionState !== "exit-done",
124-
)
125-
.forEach((activity) => {
126-
const matchActivity = input.config.activities.find(
127-
(candidate) => candidate.name === activity.name,
128-
);
129-
130-
if (!matchActivity?.loader) {
131-
return;
132-
}
133-
134-
const loaderData = (activity.context as any)?.loaderData as
135-
| SyncInspectablePromise<unknown>
136-
| undefined;
137-
const deferred = loaderData
138-
? loadPathDeferreds.get(loaderData)
139-
: undefined;
140-
141-
if (!loaderData || !deferred) {
142-
throw new Error(
143-
`Missing deferred loader data for the restored "${activity.name}" activity`,
144-
);
145-
}
146-
147-
Promise.allSettled([loaderData]).then(
148-
([loaderDataPromiseResult]) => {
149-
printLoaderDataPromiseError({
150-
promiseResult: loaderDataPromiseResult,
151-
activityName: matchActivity.name,
152-
});
153-
},
154-
);
155-
156-
try {
157-
deferred.resolve(loadData(activity.name, activity.params));
158-
} catch (error) {
159-
deferred.reject(error);
160-
}
161-
});
163+
resolveLoadPathLoaderData(actions);
164+
},
165+
onResumed({ actions }) {
166+
resolveLoadPathLoaderData(actions);
167+
},
168+
onPushed({ actions }) {
169+
resolveLoadPathLoaderData(actions);
170+
},
171+
onReplaced({ actions }) {
172+
resolveLoadPathLoaderData(actions);
162173
},
163174
onBeforePush: createBeforeRouteHandler(input, loadData),
164175
onBeforeReplace: createBeforeRouteHandler(input, loadData),
@@ -170,6 +181,10 @@ type OnBeforeRoute = NonNullable<
170181
| ReturnType<StackflowReactPlugin>["onBeforePush"]
171182
| ReturnType<StackflowReactPlugin>["onBeforeReplace"]
172183
>;
184+
type LoaderPluginActions = Parameters<
185+
NonNullable<ReturnType<StackflowReactPlugin>["onInit"]>
186+
>[0]["actions"];
187+
173188
function createBeforeRouteHandler<
174189
T extends ActivityDefinition<RegisteredActivityName>,
175190
R extends {

integrations/react/src/utils/SyncInspectablePromise.ts

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -154,28 +154,26 @@ export function defer<T>(): SyncInspectableDeferred<T> {
154154

155155
isSettled = true;
156156

157-
let source: SyncInspectablePromise<T>;
158157
try {
159-
source = resolve(value) as SyncInspectablePromise<T>;
158+
const source = resolve(value) as SyncInspectablePromise<T>;
159+
160+
if (source === promise) {
161+
rejectPromiseState(
162+
new TypeError("A promise cannot be resolved with itself"),
163+
);
164+
return;
165+
}
166+
167+
const state = inspect(source);
168+
if (state.status === PromiseStatus.FULFILLED) {
169+
fulfill(state.value);
170+
} else if (state.status === PromiseStatus.REJECTED) {
171+
rejectPromiseState(state.reason);
172+
} else {
173+
source.then(fulfill, rejectPromiseState);
174+
}
160175
} catch (error) {
161176
rejectPromiseState(error);
162-
return;
163-
}
164-
165-
if (source === promise) {
166-
rejectPromiseState(
167-
new TypeError("A promise cannot be resolved with itself"),
168-
);
169-
return;
170-
}
171-
172-
const state = inspect(source);
173-
if (state.status === PromiseStatus.FULFILLED) {
174-
fulfill(state.value);
175-
} else if (state.status === PromiseStatus.REJECTED) {
176-
rejectPromiseState(state.reason);
177-
} else {
178-
source.then(fulfill, rejectPromiseState);
179177
}
180178
},
181179
reject,

0 commit comments

Comments
 (0)