diff --git a/packages/react-meteor-data/.npm/package/npm-shrinkwrap.json b/packages/react-meteor-data/.npm/package/npm-shrinkwrap.json
index b0da3142..ac6d47a1 100644
--- a/packages/react-meteor-data/.npm/package/npm-shrinkwrap.json
+++ b/packages/react-meteor-data/.npm/package/npm-shrinkwrap.json
@@ -1,10 +1,10 @@
{
"lockfileVersion": 4,
"dependencies": {
- "lodash.isequal": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
- "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="
+ "fast-equals": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz",
+ "integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw=="
}
}
}
diff --git a/packages/react-meteor-data/.versions b/packages/react-meteor-data/.versions
index 9acbb7d7..213cd8d3 100644
--- a/packages/react-meteor-data/.versions
+++ b/packages/react-meteor-data/.versions
@@ -1,24 +1,24 @@
allow-deny@2.1.0
-babel-compiler@7.11.3
+babel-compiler@7.12.2
babel-runtime@1.5.2
base64@1.0.13
binary-heap@1.0.12
blaze@3.0.0
-boilerplate-generator@2.0.0
-callback-hook@1.6.0
+boilerplate-generator@2.0.2
+callback-hook@1.6.1
check@1.4.4
core-runtime@1.0.0
ddp@1.4.2
-ddp-client@3.1.0
+ddp-client@3.1.1
ddp-common@1.4.4
ddp-server@3.1.2
diff-sequence@1.1.3
dynamic-import@0.7.4
-ecmascript@0.16.10
+ecmascript@0.16.13
ecmascript-runtime@0.8.3
ecmascript-runtime-client@0.12.3
ecmascript-runtime-server@0.11.1
-ejson@1.1.4
+ejson@1.1.5
facts-base@1.0.2
fetch@0.1.6
geojson-utils@1.0.12
@@ -26,35 +26,35 @@ htmljs@2.0.1
id-map@1.2.0
inter-process-messaging@0.1.2
jquery@3.0.2
-local-test:react-meteor-data@4.0.0
+local-test:react-meteor-data@4.0.1-beta.2
logging@1.3.6
-meteor@2.1.0
-minimongo@2.0.2
-modern-browsers@0.2.1
+meteor@2.1.1
+minimongo@2.0.4
+modern-browsers@0.2.3
modules@0.20.3
modules-runtime@0.13.2
-mongo@2.1.1
+mongo@2.1.4
mongo-decimal@0.2.0
mongo-dev-server@1.1.1
mongo-id@1.0.9
-npm-mongo@6.10.2
+npm-mongo@6.16.1
observe-sequence@2.0.0
ordered-dict@1.2.0
promise@1.0.0
random@1.2.2
react-fast-refresh@0.2.9
-react-meteor-data@4.0.0
+react-meteor-data@4.0.1-beta.2
reactive-dict@1.3.2
reactive-var@1.0.13
reload@1.3.2
retry@1.1.1
routepolicy@1.1.2
-socket-stream-client@0.6.0
+socket-stream-client@0.6.1
test-helpers@2.0.3
-tinytest@1.3.1
+tinytest@1.3.2
tracker@1.3.4
-typescript@5.6.3
+typescript@5.6.6
underscore@1.6.4
-webapp@2.0.6
+webapp@2.0.7
webapp-hashing@1.1.2
zodern:types@1.0.13
diff --git a/packages/react-meteor-data/package.js b/packages/react-meteor-data/package.js
index c106844a..b49c3ef6 100644
--- a/packages/react-meteor-data/package.js
+++ b/packages/react-meteor-data/package.js
@@ -3,13 +3,13 @@
Package.describe({
name: 'react-meteor-data',
summary: 'React hook for reactively tracking Meteor data',
- version: '4.0.0',
+ version: '4.0.1-beta.2',
documentation: 'README.md',
git: 'https://github.com/meteor/react-packages'
})
Npm.depends({
- 'lodash.isequal': '4.5.0'
+ 'fast-equals': '5.2.2'
})
Package.onUse((api) => {
diff --git a/packages/react-meteor-data/suspense/useTracker.tests.js b/packages/react-meteor-data/suspense/useTracker.tests.js
new file mode 100644
index 00000000..5a37f9ea
--- /dev/null
+++ b/packages/react-meteor-data/suspense/useTracker.tests.js
@@ -0,0 +1,456 @@
+/* global Meteor, Tinytest */
+import React, { Suspense } from 'react';
+import { renderToString } from 'react-dom/server';
+import { Mongo } from 'meteor/mongo';
+import { render } from '@testing-library/react';
+import { useTracker, cacheMap } from './useTracker';
+
+const clearCache = async () => {
+ await new Promise((resolve) => setTimeout(resolve, 100));
+ cacheMap.clear();
+};
+
+const setupTest = (data = { id: 0, updated: 0 }) => {
+ const Coll = new Mongo.Collection(null);
+ data && Coll.insertAsync(data);
+
+ return { Coll, simpleFetch: () => Coll.find().fetchAsync() };
+};
+
+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
+ */
+runForVariants(
+ 'suspense/useTracker - Data query validation',
+ async (test, useTrackerFn) => {
+ const { simpleFetch } = setupTest();
+
+ let returnValue;
+
+ const Test = () => {
+ returnValue = useTrackerFn('TestDocs', simpleFetch);
+
+ return null;
+ };
+
+ // first return promise
+ renderToString(
+
+
+
+ );
+ test.isUndefined(
+ returnValue,
+ 'Return value should be undefined as find promise unresolved'
+ );
+ // wait promise
+ await new Promise((resolve) => setTimeout(resolve, 100));
+ // return data
+ renderToString(
+
+
+
+ );
+
+ test.equal(
+ returnValue.length,
+ 1,
+ 'Return value should be an array with one document'
+ );
+
+ await clearCache();
+ }
+);
+
+Meteor.isServer && runForVariants(
+ 'suspense/useTracker - Test proper cache invalidation',
+ 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 } });
+ await new Promise((resolve) => setTimeout(resolve, 100));
+
+ // second return 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(
+
+
+
+ );
+ renderToString(
+
+
+
+ );
+ renderToString(
+
+
+
+ );
+
+ test.equal(
+ returnValue[0].updated,
+ 1,
+ 'Return value should be an array with one document with value updated'
+ );
+
+ await clearCache();
+ }
+);
+
+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 &&
+ runForVariants(
+ 'suspense/useTracker - Test useTracker with skipUpdate',
+ async function (test, useTrackerFn) {
+ const { Coll, simpleFetch } = setupTest({ id: 0, updated: 0, other: 0 });
+
+ let returnValue;
+
+ const Test = () => {
+ returnValue = useTrackerFn('TestDocs', simpleFetch, (prev, next) => {
+ // Skip update if the document has not changed
+ return prev[0].updated === next[0].updated;
+ });
+
+ 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: { other: 1 } });
+ await new Promise((resolve) => setTimeout(resolve, 100));
+
+ // second return promise
+ renderToString(
+
+
+
+ );
+ // wait promise
+ await new Promise((resolve) => setTimeout(resolve, 100));
+ // return data
+ renderToString(
+
+
+
+ );
+
+ test.equal(
+ returnValue[0].other,
+ 0,
+ 'Return value should still not updated as skipUpdate returned true'
+ );
+
+ await clearCache();
+ }
+ );
+
+// https://github.com/meteor/react-packages/issues/454
+Meteor.isClient &&
+ runForVariants(
+ 'suspense/useTracker - Testing performance with multiple Trackers',
+ async (test, useTrackerFn) => {
+ const TestCollections = [];
+ let returnDocs = new Map();
+
+ for (let i = 0; i < 100; i++) {
+ const { Coll } = setupTest(null);
+
+ for (let i = 0; i < 100; i++) {
+ Coll.insertAsync({ id: i });
+ }
+
+ TestCollections.push(Coll);
+ }
+
+ const Test = ({ collection, index }) => {
+ const docsCount = useTrackerFn(`TestDocs${index}`, () =>
+ collection.find().fetchAsync()
+ ).length;
+
+ returnDocs.set(`TestDocs${index}`, docsCount);
+
+ return null;
+ };
+ const TestWrap = () => {
+ return (
+
+ {TestCollections.map((collection, index) => (
+
+ ))}
+
+ );
+ };
+
+ // first return promise
+ renderToString();
+ // wait promise
+ await new Promise((resolve) => setTimeout(resolve, 100));
+ // return data
+ renderToString();
+
+ test.equal(returnDocs.size, 100, 'should return 100 collections');
+
+ const docsCount = Array.from(returnDocs.values()).reduce(
+ (a, b) => a + b,
+ 0
+ );
+
+ test.equal(docsCount, 10000, 'should return 10000 documents');
+
+ await clearCache();
+ }
+ );
+
+Meteor.isServer &&
+ runForVariants(
+ 'suspense/useTracker - Test no memory leaks',
+ async function (test, useTrackerFn) {
+ const { 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(
+
+
+
+ );
+ // wait cleanup
+ await new Promise((resolve) => setTimeout(resolve, 100));
+
+ test.equal(
+ cacheMap.size,
+ 0,
+ 'Cache map should be empty as server cache should be cleared after render'
+ );
+ }
+ );
+
+Meteor.isClient &&
+ runForVariants(
+ 'suspense/useTracker - Test no memory leaks',
+ async function (test, useTrackerFn) {
+ const { simpleFetch } = setupTest({ id: 0, name: 'a' });
+
+ const Test = () => {
+ const docs = useTrackerFn('TestDocs', simpleFetch);
+
+ return
{docs[0]?.name}
;
+ };
+
+ const { queryByText, findByText, unmount } = render(, {
+ container: document.createElement('container'),
+ wrapper: TestSuspense,
+ });
+
+ test.isNotNull(
+ queryByText('Loading...'),
+ 'Throw Promise as needed to trigger the fallback.'
+ );
+
+ test.isTrue(await findByText('a'), 'Need to return data');
+
+ unmount();
+ // wait cleanup
+ await new Promise((resolve) => setTimeout(resolve, 100));
+
+ test.equal(
+ cacheMap.size,
+ 0,
+ 'Cache map should be empty as component unmounted and cache cleared'
+ );
+ }
+ );
+
+Meteor.isClient &&
+ runForVariants(
+ 'suspense/useTracker - component unmount in Strict Mode',
+ async function (test, useTrackerFn) {
+ const { simpleFetch } = setupTest();
+
+ const Test = () => {
+ useTrackerFn('TestDocs', simpleFetch);
+
+ return null;
+ };
+
+ const { queryByText, findByText, unmount } = render(, {
+ container: document.createElement('container'),
+ wrapper: TestSuspense,
+ reactStrictMode: true,
+ });
+
+ await new Promise((resolve) => setTimeout(resolve, 100));
+
+ unmount();
+
+ test.isTrue(true, 'should handle unmount correctly in Strict Mode');
+ }
+ );
diff --git a/packages/react-meteor-data/suspense/useTracker.ts b/packages/react-meteor-data/suspense/useTracker.ts
index 0ae01e75..9f6367f9 100644
--- a/packages/react-meteor-data/suspense/useTracker.ts
+++ b/packages/react-meteor-data/suspense/useTracker.ts
@@ -1,4 +1,4 @@
-import isEqual from 'lodash.isequal'
+import { strictDeepEqual } from 'fast-equals'
import { Tracker } from 'meteor/tracker'
import { type EJSON } from 'meteor/ejson'
import { type DependencyList, useEffect, useMemo, useReducer, useRef } from 'react'
@@ -38,38 +38,31 @@ 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
export type ISkipUpdate = (prev: T, next: T) => boolean
-interface TrackerRefs {
- computation?: Tracker.Computation
- isMounted: boolean
- trackerData: any
-}
-
function resolveAsync(key: string, promise: Promise | null, deps: DependencyList = []): typeof promise extends null ? null : T {
const cached = cacheMap.get(key)
useEffect(() =>
() => {
setTimeout(() => {
- if (cached !== undefined && isEqual(cached.deps, deps)) cacheMap.delete(key)
+ if (cached !== undefined && strictDeepEqual(cached.deps, deps)) cacheMap.delete(key)
}, 0)
}, [cached, key, ...deps])
if (promise === null) return null
if (cached !== undefined) {
- if ('error' in cached) throw cached.error
- if ('result' in cached) {
- const result = cached.result as T
+ if (Meteor.isServer && ('error' in cached || 'result' in cached)) {
setTimeout(() => {
cacheMap.delete(key)
}, 0)
- return result
}
+ if ('error' in cached) throw cached.error
+ if ('result' in cached) return cached.result as T
throw cached.promise
}
@@ -91,19 +84,16 @@ function resolveAsync(key: string, promise: Promise | null, deps: Dependen
throw entry.promise
}
-export function useTrackerNoDeps(key: string, reactiveFn: IReactiveFn, skipUpdate: ISkipUpdate = null): T {
- const { current: refs } = useRef({
+export function useTrackerSuspenseNoDeps(key: string, reactiveFn: IReactiveFn, skipUpdate: ISkipUpdate = null): T {
+ const { current: refs } = useRef<{
+ isMounted: boolean
+ computation?: Tracker.Computation
+ trackerData: any
+ }>({
isMounted: false,
trackerData: null
})
- const forceUpdate = useForceUpdate()
-
- // Without deps, always dispose and recreate the computation with every render.
- if (refs.computation != null) {
- refs.computation.stop()
- // @ts-expect-error This makes TS think ref.computation is "never" set
- delete refs.computation
- }
+ 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.
@@ -111,59 +101,41 @@ export function useTrackerNoDeps(key: string, reactiveFn: IReactiveFn
- Tracker.autorun(async (c: Tracker.Computation) => {
- refs.computation = c
+ Tracker.autorun(async (comp: Tracker.Computation) => {
+ if (refs.computation) {
+ refs.computation.stop()
+ delete refs.computation
+ }
+
+ refs.computation = comp
+
+ const data: Promise = Tracker.withComputation(comp, async () => reactiveFn(comp))
- const data: Promise = Tracker.withComputation(c, async () => reactiveFn(c))
- if (c.firstRun) {
+ if (comp.firstRun) {
// Always run the reactiveFn on firstRun
refs.trackerData = data
- } else if (!skipUpdate || !skipUpdate(await refs.trackerData, await data)) {
- // For any reactive change, forceUpdate and let the next render rebuild the computation.
- forceUpdate()
- }
- }))
+ } else {
+ const dataResult = await data;
- // To clean up side effects in render, stop the computation immediately
- if (!refs.isMounted) {
- Meteor.defer(() => {
- if (!refs.isMounted && (refs.computation != null)) {
- refs.computation.stop()
- delete refs.computation
+ if (!skipUpdate || !skipUpdate(await refs.trackerData, dataResult)) {
+ const cached = cacheMap.get(key);
+ cached && (cached.result = dataResult);
+ refs.isMounted && forceUpdate()
+ }
}
- })
- }
+ }))
useEffect(() => {
// Let subsequent renders know we are mounted (render is committed).
refs.isMounted = true
- // In some cases, the useEffect hook will run before Meteor.defer, such as
- // when React.lazy is used. In those cases, we might as well leave the
- // computation alone!
- if (refs.computation == null) {
- // Render is committed, but we no longer have a computation. Invoke
- // forceUpdate and let the next render recreate the computation.
- if (!skipUpdate) {
- forceUpdate()
- } else {
- Tracker.nonreactive(() =>
- Tracker.autorun(async (c: Tracker.Computation) => {
- const data = Tracker.withComputation(c, async () => reactiveFn(c))
-
- refs.computation = c
- if (!skipUpdate(await refs.trackerData, await data)) {
- // For any reactive change, forceUpdate and let the next render rebuild the computation.
- forceUpdate()
- }
- }))
- }
- }
-
// stop the computation on unmount
return () => {
- refs.computation?.stop()
- delete refs.computation
+ if (refs.computation) {
+ refs.computation.stop()
+ delete refs.computation
+ }
+
refs.isMounted = false
}
}, [])
@@ -171,16 +143,20 @@ export function useTrackerNoDeps(key: string, reactiveFn: IReactiveFn(key: string, reactiveFn: IReactiveFn, deps: DependencyList, skipUpdate: ISkipUpdate = null): T => {
- const forceUpdate = useForceUpdate()
+export const useTrackerSuspenseWithDeps =
+ (key: string, reactiveFn: IReactiveFn, deps: DependencyList, skipUpdate?: ISkipUpdate = null): T => {
+ const [version, forceUpdate] = useForceUpdate()
const { current: refs } = useRef<{
reactiveFn: IReactiveFn
- data?: Promise
- comp?: Tracker.Computation
- isMounted?: boolean
- }>({ reactiveFn })
+ isMounted: boolean
+ trackerData?: Promise
+ computation?: Tracker.Computation
+ }>({
+ reactiveFn,
+ isMounted: false,
+ trackerData: null
+ })
// keep reactiveFn ref fresh
refs.reactiveFn = reactiveFn
@@ -188,88 +164,73 @@ export const useTrackerWithDeps =
useMemo(() => {
// To jive with the lifecycle interplay between Tracker/Subscribe, run the
// reactive function in a computation, then stop it, to force flush cycle.
- const comp = Tracker.nonreactive(
- () => Tracker.autorun(async (c: Tracker.Computation) => {
- const data = Tracker.withComputation(c, async () => refs.reactiveFn(c))
- if (c.firstRun) {
- refs.data = data
- } else if (!skipUpdate || !skipUpdate(await refs.data, await data)) {
- refs.data = data
- forceUpdate()
+ Tracker.nonreactive(
+ () => Tracker.autorun(async (comp: Tracker.Computation) => {
+ if (refs.computation) {
+ refs.computation.stop()
+ delete refs.computation
+ }
+
+ refs.computation = comp
+
+ const data = Tracker.withComputation(comp, async () => refs.reactiveFn(comp))
+
+ if (comp.firstRun) {
+ refs.trackerData = data
+ } else {
+ const dataResult = await data;
+
+ if (!skipUpdate || !skipUpdate(await refs.trackerData, dataResult)) {
+ const cached = cacheMap.get(key);
+ cached && (cached.result = dataResult);
+ refs.isMounted && forceUpdate()
+ }
}
})
)
-
- // Stop the computation immediately to avoid creating side effects in render.
- // refers to this issues:
- // https://github.com/meteor/react-packages/issues/382
- // https://github.com/meteor/react-packages/issues/381
- if (refs.comp != null) refs.comp.stop()
-
- // In some cases, the useEffect hook will run before Meteor.defer, such as
- // when React.lazy is used. This will allow it to be stopped earlier in
- // useEffect if needed.
- refs.comp = comp
- // To avoid creating side effects in render, stop the computation immediately
- Meteor.defer(() => {
- if (!refs.isMounted && (refs.comp != null)) {
- refs.comp.stop()
- delete refs.comp
- }
- })
- }, deps)
+ }, [...deps, version])
useEffect(() => {
// Let subsequent renders know we are mounted (render is committed).
refs.isMounted = true
- if (refs.comp == null) {
- refs.comp = Tracker.nonreactive(
- () => Tracker.autorun(async (c) => {
- const data: Promise = Tracker.withComputation(c, async () => refs.reactiveFn())
- if (!skipUpdate || !skipUpdate(await refs.data, await data)) {
- refs.data = data
- forceUpdate()
- }
- })
- )
- }
-
return () => {
- // @ts-expect-error
- refs.comp.stop()
- delete refs.comp
+ if (refs.computation) {
+ refs.computation.stop()
+ delete refs.computation
+ }
+
refs.isMounted = false
}
}, deps)
- return resolveAsync(key, refs.data as Promise, deps)
+ return resolveAsync(key, refs.trackerData, deps)
}
-function useTrackerClient(key: string, reactiveFn: IReactiveFn, skipUpdate?: ISkipUpdate): T
-function useTrackerClient(key: string, reactiveFn: IReactiveFn, deps?: DependencyList, skipUpdate?: ISkipUpdate): T
-function useTrackerClient(key: string, reactiveFn: IReactiveFn, deps: DependencyList | ISkipUpdate = null, skipUpdate: ISkipUpdate = null): T {
+export function useTrackerSuspenseClient(key: string, reactiveFn: IReactiveFn, skipUpdate?: ISkipUpdate): T
+export function useTrackerSuspenseClient(key: string, reactiveFn: IReactiveFn, deps?: DependencyList, skipUpdate?: ISkipUpdate): T
+export function useTrackerSuspenseClient(key: string, reactiveFn: IReactiveFn, deps: DependencyList | ISkipUpdate = null, skipUpdate: ISkipUpdate = null): T {
if (deps === null || deps === undefined || !Array.isArray(deps)) {
if (typeof deps === 'function') {
skipUpdate = deps
}
- return useTrackerNoDeps(key, reactiveFn, skipUpdate)
+ return useTrackerSuspenseNoDeps(key, reactiveFn, skipUpdate)
} else {
- return useTrackerWithDeps(key, reactiveFn, deps, skipUpdate)
+ return useTrackerSuspenseWithDeps(key, reactiveFn, deps, skipUpdate)
}
}
-const useTrackerServer: typeof useTrackerClient = (key, reactiveFn) => {
+export const useTrackerSuspenseServer: typeof useTrackerSuspenseClient = (key, reactiveFn) => {
return resolveAsync(key, Tracker.nonreactive(reactiveFn))
}
// When rendering on the server, we don't want to use the Tracker.
// We only do the first rendering on the server so we can get the data right away
-const _useTracker = Meteor.isServer
- ? useTrackerServer
- : useTrackerClient
+export const useTracker = Meteor.isServer
+ ? useTrackerSuspenseServer
+ : useTrackerSuspenseClient
-function useTrackerDev(key: string, reactiveFn, deps: DependencyList | null = null, skipUpdate = null) {
+function useTrackerDev(key: string, reactiveFn: any, deps: DependencyList | null = null, skipUpdate = null) {
function warn(expects: string, pos: string, arg: string, type: string) {
console.warn(
`Warning: useTracker expected a ${expects} in it\'s ${pos} argument ` +
@@ -293,11 +254,11 @@ function useTrackerDev(key: string, reactiveFn, deps: DependencyList | null = nu
}
}
- const data = _useTracker(key, reactiveFn, deps, skipUpdate)
+ const data = useTracker(key, reactiveFn, deps, skipUpdate)
checkCursor(data)
return data
}
-export const useTracker = Meteor.isDevelopment
- ? useTrackerDev as typeof useTrackerClient
- : _useTracker
+export default Meteor.isDevelopment
+ ? useTrackerDev
+ : useTracker
\ No newline at end of file
diff --git a/packages/react-meteor-data/tests.js b/packages/react-meteor-data/tests.js
index 13539827..8fc74c85 100644
--- a/packages/react-meteor-data/tests.js
+++ b/packages/react-meteor-data/tests.js
@@ -1,5 +1,6 @@
import './useTracker.tests.js'
import './withTracker.tests.js'
import './useFind.tests.js'
+import './suspense/useTracker.tests.js'
import './suspense/useSubscribe.tests.js'
import './suspense/useFind.tests.js'
diff --git a/packages/react-meteor-data/withTracker.tsx b/packages/react-meteor-data/withTracker.tsx
index 120cc58f..e6301ee8 100644
--- a/packages/react-meteor-data/withTracker.tsx
+++ b/packages/react-meteor-data/withTracker.tsx
@@ -1,5 +1,6 @@
import React, { forwardRef, memo } from 'react';
import { useTracker } from './useTracker';
+import { Meteor } from 'meteor/meteor';
type ReactiveFn = (props: object) => any;
type ReactiveOptions = {
@@ -10,6 +11,9 @@ type ReactiveOptions = {
export const withTracker = (options: ReactiveFn | ReactiveOptions) => {
return (Component: React.ComponentType) => {
+ if (Meteor.isDevelopment) {
+ console.warn('It appears that you are using withTracker. This approach has been deprecated and will be removed in future versions of the package. Please migrate to using hooks.')
+ }
const getMeteorData = typeof options === 'function'
? options
: options.getMeteorData;
diff --git a/packages/react-template-helper/.versions b/packages/react-template-helper/.versions
index 4a275cb0..882811c3 100644
--- a/packages/react-template-helper/.versions
+++ b/packages/react-template-helper/.versions
@@ -1,26 +1,26 @@
-babel-compiler@7.11.0
+babel-compiler@7.12.2
babel-runtime@1.5.2
base64@1.0.13
blaze@3.0.0
blaze-tools@2.0.0
-caching-compiler@2.0.0
+caching-compiler@2.0.1
caching-html-compiler@2.0.0
-check@1.4.2
+check@1.4.4
core-runtime@1.0.0
diff-sequence@1.1.3
dynamic-import@0.7.4
-ecmascript@0.16.9
-ecmascript-runtime@0.8.2
-ecmascript-runtime-client@0.12.2
+ecmascript@0.16.13
+ecmascript-runtime@0.8.3
+ecmascript-runtime-client@0.12.3
ecmascript-runtime-server@0.11.1
-ejson@1.1.4
-fetch@0.1.5
+ejson@1.1.5
+fetch@0.1.6
html-tools@2.0.0
htmljs@2.0.1
inter-process-messaging@0.1.2
-meteor@2.0.1
-modern-browsers@0.1.11
-modules@0.20.1
+meteor@2.1.1
+modern-browsers@0.2.3
+modules@0.20.3
modules-runtime@0.13.2
mongo-id@1.0.9
observe-sequence@2.0.0
@@ -28,7 +28,7 @@ ordered-dict@1.2.0
promise@1.0.0
random@1.2.2
react-fast-refresh@0.2.9
-react-template-helper@0.3.0
+react-template-helper@0.4.0-beta.0
reactive-var@1.0.13
spacebars@2.0.0
spacebars-compiler@2.0.0
@@ -38,6 +38,6 @@ templating-runtime@2.0.0
templating-tools@2.0.0
tmeasday:check-npm-versions@2.0.0
tracker@1.3.4
-typescript@5.4.3
+typescript@5.6.6
underscore@1.6.4
zodern:types@1.0.13
diff --git a/packages/react-template-helper/package.js b/packages/react-template-helper/package.js
index e4fc7830..9652f312 100644
--- a/packages/react-template-helper/package.js
+++ b/packages/react-template-helper/package.js
@@ -1,6 +1,6 @@
Package.describe({
name: 'react-template-helper',
- version: '0.3.0',
+ version: '0.4.0-beta.0',
// Brief, one-line summary of the package.
summary: 'Use React components in native Meteor templates',
// URL to the Git repository containing the source code for this package.
diff --git a/packages/react-template-helper/react-template-helper.js b/packages/react-template-helper/react-template-helper.js
index 8c40b3ca..e432c87f 100644
--- a/packages/react-template-helper/react-template-helper.js
+++ b/packages/react-template-helper/react-template-helper.js
@@ -1,7 +1,7 @@
import { checkNpmVersions } from 'meteor/tmeasday:check-npm-versions';
checkNpmVersions({
- 'react': '15.3 - 18',
- 'react-dom': '15.3 - 18'
+ 'react': '15.3 - 19',
+ 'react-dom': '15.3 - 19'
}, 'react-template-helper');
const React = require('react');