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');