Skip to content

Commit 97fb366

Browse files
authored
Merge pull request #457 from welkinwong/feature/4.0.1
fix(useTrackerSuspense): Resolve unresponsiveness in withDeps and optimize subsequent updates
2 parents d5c07be + 48e2093 commit 97fb366

File tree

2 files changed

+124
-30
lines changed

2 files changed

+124
-30
lines changed

packages/react-meteor-data/suspense/useTracker.tests.js

Lines changed: 105 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,37 @@ const TestSuspense = ({ children }) => {
2121
return <Suspense fallback={<div>Loading...</div>}>{children}</Suspense>;
2222
};
2323

24+
const trackerVariants = [
25+
{
26+
label: 'default',
27+
useTrackerFn: (key, fn, skipUpdate) => useTracker(key, fn, skipUpdate),
28+
},
29+
{
30+
label: 'with deps',
31+
useTrackerFn: (key, fn, skipUpdate) => useTracker(key, fn, [], skipUpdate),
32+
},
33+
];
34+
35+
const runForVariants = (name, testBody) => {
36+
trackerVariants.forEach(({ label, useTrackerFn }) => {
37+
Tinytest.addAsync(`${name} [${label}]`, (test) =>
38+
testBody(test, useTrackerFn)
39+
);
40+
});
41+
};
42+
2443
/**
2544
* Test for useTracker with Suspense
2645
*/
27-
Tinytest.addAsync(
46+
runForVariants(
2847
'suspense/useTracker - Data query validation',
29-
async (test) => {
48+
async (test, useTrackerFn) => {
3049
const { simpleFetch } = setupTest();
3150

3251
let returnValue;
3352

3453
const Test = () => {
35-
returnValue = useTracker('TestDocs', simpleFetch);
54+
returnValue = useTrackerFn('TestDocs', simpleFetch);
3655

3756
return null;
3857
};
@@ -66,15 +85,15 @@ Tinytest.addAsync(
6685
}
6786
);
6887

69-
Tinytest.addAsync(
88+
Meteor.isServer && runForVariants(
7089
'suspense/useTracker - Test proper cache invalidation',
71-
async function (test) {
90+
async function (test, useTrackerFn) {
7291
const { Coll, simpleFetch } = setupTest();
7392

7493
let returnValue;
7594

7695
const Test = () => {
77-
returnValue = useTracker('TestDocs', simpleFetch);
96+
returnValue = useTrackerFn('TestDocs', simpleFetch);
7897
return null;
7998
};
8099

@@ -144,16 +163,84 @@ Tinytest.addAsync(
144163
}
145164
);
146165

166+
Meteor.isClient && runForVariants(
167+
'suspense/useTracker - Test responsive behavior',
168+
async function (test, useTrackerFn) {
169+
const { Coll, simpleFetch } = setupTest();
170+
171+
let returnValue;
172+
173+
const Test = () => {
174+
returnValue = useTrackerFn('TestDocs', simpleFetch);
175+
return null;
176+
};
177+
178+
// first return promise
179+
renderToString(
180+
<TestSuspense>
181+
<Test />
182+
</TestSuspense>
183+
);
184+
// wait promise
185+
await new Promise((resolve) => setTimeout(resolve, 100));
186+
// return data
187+
renderToString(
188+
<TestSuspense>
189+
<Test />
190+
</TestSuspense>
191+
);
192+
193+
test.equal(
194+
returnValue[0].updated,
195+
0,
196+
'Return value should be an array with initial value as find promise resolved'
197+
);
198+
199+
Coll.updateAsync({ id: 0 }, { $inc: { updated: 1 } });
200+
201+
// second await promise
202+
renderToString(
203+
<TestSuspense>
204+
<Test />
205+
</TestSuspense>
206+
);
207+
208+
test.equal(
209+
returnValue[0].updated,
210+
0,
211+
'Return value should still not updated as second find promise unresolved'
212+
);
213+
214+
// wait promise
215+
await new Promise((resolve) => setTimeout(resolve, 100));
216+
217+
// return data
218+
renderToString(
219+
<TestSuspense>
220+
<Test />
221+
</TestSuspense>
222+
);
223+
224+
test.equal(
225+
returnValue[0].updated,
226+
1,
227+
'Return value should be an array with one document with value updated'
228+
);
229+
230+
await clearCache();
231+
}
232+
);
233+
147234
Meteor.isClient &&
148-
Tinytest.addAsync(
235+
runForVariants(
149236
'suspense/useTracker - Test useTracker with skipUpdate',
150-
async function (test) {
237+
async function (test, useTrackerFn) {
151238
const { Coll, simpleFetch } = setupTest({ id: 0, updated: 0, other: 0 });
152239

153240
let returnValue;
154241

155242
const Test = () => {
156-
returnValue = useTracker('TestDocs', simpleFetch, (prev, next) => {
243+
returnValue = useTrackerFn('TestDocs', simpleFetch, (prev, next) => {
157244
// Skip update if the document has not changed
158245
return prev[0].updated === next[0].updated;
159246
});
@@ -212,9 +299,9 @@ Meteor.isClient &&
212299

213300
// https://github.com/meteor/react-packages/issues/454
214301
Meteor.isClient &&
215-
Tinytest.addAsync(
302+
runForVariants(
216303
'suspense/useTracker - Testing performance with multiple Trackers',
217-
async (test) => {
304+
async (test, useTrackerFn) => {
218305
const TestCollections = [];
219306
let returnDocs = new Map();
220307

@@ -229,7 +316,7 @@ Meteor.isClient &&
229316
}
230317

231318
const Test = ({ collection, index }) => {
232-
const docsCount = useTracker(`TestDocs${index}`, () =>
319+
const docsCount = useTrackerFn(`TestDocs${index}`, () =>
233320
collection.find().fetchAsync()
234321
).length;
235322

@@ -268,15 +355,15 @@ Meteor.isClient &&
268355
);
269356

270357
Meteor.isServer &&
271-
Tinytest.addAsync(
358+
runForVariants(
272359
'suspense/useTracker - Test no memory leaks',
273-
async function (test) {
360+
async function (test, useTrackerFn) {
274361
const { simpleFetch } = setupTest();
275362

276363
let returnValue;
277364

278365
const Test = () => {
279-
returnValue = useTracker('TestDocs', simpleFetch);
366+
returnValue = useTrackerFn('TestDocs', simpleFetch);
280367

281368
return null;
282369
};
@@ -307,13 +394,13 @@ Meteor.isServer &&
307394
);
308395

309396
Meteor.isClient &&
310-
Tinytest.addAsync(
397+
runForVariants(
311398
'suspense/useTracker - Test no memory leaks',
312-
async function (test) {
399+
async function (test, useTrackerFn) {
313400
const { simpleFetch } = setupTest({ id: 0, name: 'a' });
314401

315402
const Test = () => {
316-
const docs = useTracker('TestDocs', simpleFetch);
403+
const docs = useTrackerFn('TestDocs', simpleFetch);
317404

318405
return <div>{docs[0]?.name}</div>;
319406
};

packages/react-meteor-data/suspense/useTracker.ts

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ interface Entry {
3838
// Used to create a forceUpdate from useReducer. Forces update by
3939
// incrementing a number whenever the dispatch method is invoked.
4040
const fur = (x: number): number => x + 1
41-
const useForceUpdate = () => useReducer(fur, 0)[1]
41+
const useForceUpdate = () => useReducer(fur, 0)
4242

4343
export type IReactiveFn<T> = (c?: Tracker.Computation) => Promise<T>
4444

@@ -93,7 +93,7 @@ export function useTrackerSuspenseNoDeps<T = any>(key: string, reactiveFn: IReac
9393
isMounted: false,
9494
trackerData: null
9595
})
96-
const forceUpdate = useForceUpdate()
96+
const [, forceUpdate] = useForceUpdate()
9797

9898
// Use Tracker.nonreactive in case we are inside a Tracker Computation.
9999
// This can happen if someone calls `ReactDOM.render` inside a Computation.
@@ -114,11 +114,14 @@ export function useTrackerSuspenseNoDeps<T = any>(key: string, reactiveFn: IReac
114114
if (comp.firstRun) {
115115
// Always run the reactiveFn on firstRun
116116
refs.trackerData = data
117-
} else if (!skipUpdate || !skipUpdate(await refs.trackerData, await data)) {
118-
cacheMap.delete(key)
117+
} else {
118+
const dataResult = await data;
119119

120-
// For any reactive change, forceUpdate and let the next render rebuild the computation.
121-
refs.isMounted && forceUpdate()
120+
if (!skipUpdate || !skipUpdate(await refs.trackerData, dataResult)) {
121+
const cached = cacheMap.get(key);
122+
cached && (cached.result = dataResult);
123+
refs.isMounted && forceUpdate()
124+
}
122125
}
123126
}))
124127

@@ -139,7 +142,7 @@ export function useTrackerSuspenseNoDeps<T = any>(key: string, reactiveFn: IReac
139142

140143
export const useTrackerSuspenseWithDeps =
141144
<T = any>(key: string, reactiveFn: IReactiveFn<T>, deps: DependencyList, skipUpdate?: ISkipUpdate<T> = null): T => {
142-
const forceUpdate = useForceUpdate()
145+
const [version, forceUpdate] = useForceUpdate()
143146

144147
const { current: refs } = useRef<{
145148
reactiveFn: IReactiveFn<T>
@@ -171,14 +174,18 @@ export const useTrackerSuspenseWithDeps =
171174

172175
if (comp.firstRun) {
173176
refs.trackerData = data
174-
} else if (!skipUpdate || !skipUpdate(await refs.trackerData, await data)) {
175-
cacheMap.delete(key)
176-
177-
refs.isMounted && forceUpdate()
177+
} else {
178+
const dataResult = await data;
179+
180+
if (!skipUpdate || !skipUpdate(await refs.trackerData, dataResult)) {
181+
const cached = cacheMap.get(key);
182+
cached && (cached.result = dataResult);
183+
refs.isMounted && forceUpdate()
184+
}
178185
}
179186
})
180187
)
181-
}, deps)
188+
}, [...deps, version])
182189

183190
useEffect(() => {
184191
// Let subsequent renders know we are mounted (render is committed).

0 commit comments

Comments
 (0)