diff --git a/packages/react-meteor-data/suspense/useTracker.tests.js b/packages/react-meteor-data/suspense/useTracker.tests.js
index c20fe05c..e1c07c77 100644
--- a/packages/react-meteor-data/suspense/useTracker.tests.js
+++ b/packages/react-meteor-data/suspense/useTracker.tests.js
@@ -21,18 +21,37 @@ const TestSuspense = ({ children }) => {
return Loading...}>{children};
};
+const trackerVariants = [
+ {
+ label: 'default',
+ useTrackerFn: (key, fn, skipUpdate) => useTracker(key, fn, skipUpdate),
+ },
+ {
+ label: 'with deps',
+ useTrackerFn: (key, fn, skipUpdate) => useTracker(key, fn, [], skipUpdate),
+ },
+];
+
+const runForVariants = (name, testBody) => {
+ trackerVariants.forEach(({ label, useTrackerFn }) => {
+ Tinytest.addAsync(`${name} [${label}]`, (test) =>
+ testBody(test, useTrackerFn)
+ );
+ });
+};
+
/**
* Test for useTracker with Suspense
*/
-Tinytest.addAsync(
+runForVariants(
'suspense/useTracker - Data query validation',
- async (test) => {
+ async (test, useTrackerFn) => {
const { simpleFetch } = setupTest();
let returnValue;
const Test = () => {
- returnValue = useTracker('TestDocs', simpleFetch);
+ returnValue = useTrackerFn('TestDocs', simpleFetch);
return null;
};
@@ -66,15 +85,15 @@ Tinytest.addAsync(
}
);
-Tinytest.addAsync(
+Meteor.isServer && runForVariants(
'suspense/useTracker - Test proper cache invalidation',
- async function (test) {
+ async function (test, useTrackerFn) {
const { Coll, simpleFetch } = setupTest();
let returnValue;
const Test = () => {
- returnValue = useTracker('TestDocs', simpleFetch);
+ returnValue = useTrackerFn('TestDocs', simpleFetch);
return null;
};
@@ -144,16 +163,84 @@ Tinytest.addAsync(
}
);
+Meteor.isClient && runForVariants(
+ 'suspense/useTracker - Test responsive behavior',
+ async function (test, useTrackerFn) {
+ const { Coll, simpleFetch } = setupTest();
+
+ let returnValue;
+
+ const Test = () => {
+ returnValue = useTrackerFn('TestDocs', simpleFetch);
+ return null;
+ };
+
+ // first return promise
+ renderToString(
+
+
+
+ );
+ // wait promise
+ await new Promise((resolve) => setTimeout(resolve, 100));
+ // return data
+ renderToString(
+
+
+
+ );
+
+ test.equal(
+ returnValue[0].updated,
+ 0,
+ 'Return value should be an array with initial value as find promise resolved'
+ );
+
+ Coll.updateAsync({ id: 0 }, { $inc: { updated: 1 } });
+
+ // second await promise
+ renderToString(
+
+
+
+ );
+
+ test.equal(
+ returnValue[0].updated,
+ 0,
+ 'Return value should still not updated as second find promise unresolved'
+ );
+
+ // wait promise
+ await new Promise((resolve) => setTimeout(resolve, 100));
+
+ // return data
+ renderToString(
+
+
+
+ );
+
+ test.equal(
+ returnValue[0].updated,
+ 1,
+ 'Return value should be an array with one document with value updated'
+ );
+
+ await clearCache();
+ }
+);
+
Meteor.isClient &&
- Tinytest.addAsync(
+ runForVariants(
'suspense/useTracker - Test useTracker with skipUpdate',
- async function (test) {
+ async function (test, useTrackerFn) {
const { Coll, simpleFetch } = setupTest({ id: 0, updated: 0, other: 0 });
let returnValue;
const Test = () => {
- returnValue = useTracker('TestDocs', simpleFetch, (prev, next) => {
+ returnValue = useTrackerFn('TestDocs', simpleFetch, (prev, next) => {
// Skip update if the document has not changed
return prev[0].updated === next[0].updated;
});
@@ -212,9 +299,9 @@ Meteor.isClient &&
// https://github.com/meteor/react-packages/issues/454
Meteor.isClient &&
- Tinytest.addAsync(
+ runForVariants(
'suspense/useTracker - Testing performance with multiple Trackers',
- async (test) => {
+ async (test, useTrackerFn) => {
const TestCollections = [];
let returnDocs = new Map();
@@ -229,7 +316,7 @@ Meteor.isClient &&
}
const Test = ({ collection, index }) => {
- const docsCount = useTracker(`TestDocs${index}`, () =>
+ const docsCount = useTrackerFn(`TestDocs${index}`, () =>
collection.find().fetchAsync()
).length;
@@ -268,15 +355,15 @@ Meteor.isClient &&
);
Meteor.isServer &&
- Tinytest.addAsync(
+ runForVariants(
'suspense/useTracker - Test no memory leaks',
- async function (test) {
+ async function (test, useTrackerFn) {
const { simpleFetch } = setupTest();
let returnValue;
const Test = () => {
- returnValue = useTracker('TestDocs', simpleFetch);
+ returnValue = useTrackerFn('TestDocs', simpleFetch);
return null;
};
@@ -307,13 +394,13 @@ Meteor.isServer &&
);
Meteor.isClient &&
- Tinytest.addAsync(
+ runForVariants(
'suspense/useTracker - Test no memory leaks',
- async function (test) {
+ async function (test, useTrackerFn) {
const { simpleFetch } = setupTest({ id: 0, name: 'a' });
const Test = () => {
- const docs = useTracker('TestDocs', simpleFetch);
+ const docs = useTrackerFn('TestDocs', simpleFetch);
return
{docs[0]?.name}
;
};
diff --git a/packages/react-meteor-data/suspense/useTracker.ts b/packages/react-meteor-data/suspense/useTracker.ts
index 2e98e226..24b29620 100644
--- a/packages/react-meteor-data/suspense/useTracker.ts
+++ b/packages/react-meteor-data/suspense/useTracker.ts
@@ -38,7 +38,7 @@ interface Entry {
// Used to create a forceUpdate from useReducer. Forces update by
// incrementing a number whenever the dispatch method is invoked.
const fur = (x: number): number => x + 1
-const useForceUpdate = () => useReducer(fur, 0)[1]
+const useForceUpdate = () => useReducer(fur, 0)
export type IReactiveFn = (c?: Tracker.Computation) => Promise
@@ -93,7 +93,7 @@ export function useTrackerSuspenseNoDeps(key: string, reactiveFn: IReac
isMounted: false,
trackerData: null
})
- const forceUpdate = useForceUpdate()
+ const [, forceUpdate] = useForceUpdate()
// Use Tracker.nonreactive in case we are inside a Tracker Computation.
// This can happen if someone calls `ReactDOM.render` inside a Computation.
@@ -114,11 +114,14 @@ export function useTrackerSuspenseNoDeps(key: string, reactiveFn: IReac
if (comp.firstRun) {
// Always run the reactiveFn on firstRun
refs.trackerData = data
- } else if (!skipUpdate || !skipUpdate(await refs.trackerData, await data)) {
- cacheMap.delete(key)
+ } else {
+ const dataResult = await data;
- // For any reactive change, forceUpdate and let the next render rebuild the computation.
- refs.isMounted && forceUpdate()
+ if (!skipUpdate || !skipUpdate(await refs.trackerData, dataResult)) {
+ const cached = cacheMap.get(key);
+ cached && (cached.result = dataResult);
+ refs.isMounted && forceUpdate()
+ }
}
}))
@@ -139,7 +142,7 @@ export function useTrackerSuspenseNoDeps(key: string, reactiveFn: IReac
export const useTrackerSuspenseWithDeps =
(key: string, reactiveFn: IReactiveFn, deps: DependencyList, skipUpdate?: ISkipUpdate = null): T => {
- const forceUpdate = useForceUpdate()
+ const [version, forceUpdate] = useForceUpdate()
const { current: refs } = useRef<{
reactiveFn: IReactiveFn
@@ -171,14 +174,18 @@ export const useTrackerSuspenseWithDeps =
if (comp.firstRun) {
refs.trackerData = data
- } else if (!skipUpdate || !skipUpdate(await refs.trackerData, await data)) {
- cacheMap.delete(key)
-
- refs.isMounted && forceUpdate()
+ } else {
+ const dataResult = await data;
+
+ if (!skipUpdate || !skipUpdate(await refs.trackerData, dataResult)) {
+ const cached = cacheMap.get(key);
+ cached && (cached.result = dataResult);
+ refs.isMounted && forceUpdate()
+ }
}
})
)
- }, deps)
+ }, [...deps, version])
useEffect(() => {
// Let subsequent renders know we are mounted (render is committed).