diff --git a/packages/react-meteor-data/useTracker.js b/packages/react-meteor-data/useTracker.js index 1bbb851a..e140cf49 100644 --- a/packages/react-meteor-data/useTracker.js +++ b/packages/react-meteor-data/useTracker.js @@ -1,6 +1,8 @@ +/* global Meteor, Package, Tracker */ import React, { useState, useEffect, useRef } from 'react'; -import { Tracker } from 'meteor/tracker'; -import { Meteor } from 'meteor/meteor'; + +// Use React.warn() if available (should ship in React 16.9). +const warn = React.warn || console.warn.bind(console); // Warns if data is a Mongo.Cursor or a POJO containing a Mongo.Cursor. function checkCursor(data) { @@ -8,8 +10,7 @@ function checkCursor(data) { if (Package.mongo && Package.mongo.Mongo && data && typeof data === 'object') { if (data instanceof Package.mongo.Mongo.Cursor) { shouldWarn = true; - } - else if (Object.getPrototypeOf(data) === Object.prototype) { + } else if (Object.getPrototypeOf(data) === Object.prototype) { Object.keys(data).forEach((key) => { if (data[key] instanceof Package.mongo.Mongo.Cursor) { shouldWarn = true; @@ -18,8 +19,6 @@ function checkCursor(data) { } } if (shouldWarn) { - // Use React.warn() if available (should ship in React 16.9). - const warn = React.warn || console.warn.bind(console); warn( 'Warning: your reactive function is returning a Mongo cursor. ' + 'This value will not be reactive. You probably want to call ' @@ -49,8 +48,6 @@ function areHookInputsEqual(nextDeps, prevDeps) { if (!Array.isArray(nextDeps)) { if (Meteor.isDevelopment) { - // Use React.warn() if available (should ship in React 16.9). - const warn = React.warn || console.warn.bind(console); warn( 'Warning: useTracker expected an dependency value of ' + `type array but got type of ${typeof nextDeps} instead.` @@ -76,10 +73,11 @@ function areHookInputsEqual(nextDeps, prevDeps) { let uniqueCounter = 0; -function useTracker(reactiveFn, deps) { +function useTracker(reactiveFn, deps, cleanup) { const previousDeps = useRef(); const computation = useRef(); const trackerData = useRef(); + const cleanupRef = useRef(); const [, forceUpdate] = useState(); @@ -88,6 +86,9 @@ function useTracker(reactiveFn, deps) { computation.current.stop(); computation.current = null; } + if (cleanupRef.current) { + cleanupRef.current(); + } }; // this is called like at componentWillMount and componentWillUpdate equally @@ -106,7 +107,7 @@ function useTracker(reactiveFn, deps) { Tracker.autorun((c) => { if (c.firstRun) { const data = reactiveFn(); - Meteor.isDevelopment && checkCursor(data); + if (Meteor.isDevelopment) checkCursor(data); // store the deps for comparison on next render previousDeps.current = deps; @@ -133,13 +134,16 @@ function useTracker(reactiveFn, deps) { )); } + // NOTE: Make sure to set cleanupRef AFTER a possible dispose invokes the last one, because + // when/if deps change, we'll likely have a new cleanup method comint with it. So we want + // to invoke the last current one before we reset it. + cleanupRef.current = cleanup; + // stop the computation on unmount only useEffect(() => { if (Meteor.isDevelopment && deps !== null && deps !== undefined && !Array.isArray(deps)) { - // Use React.warn() if available (should ship in React 16.9). - const warn = React.warn || console.warn.bind(console); warn( 'Warning: useTracker expected an initial dependency value of ' + `type array but got type of ${typeof deps} instead.` @@ -154,8 +158,8 @@ function useTracker(reactiveFn, deps) { // 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 -function useTracker__server(reactiveFn, deps) { +function useTrackerServer(reactiveFn) { return reactiveFn(); } -export default (Meteor.isServer ? useTracker__server : useTracker); +export default (Meteor.isServer ? useTrackerServer : useTracker);