I am trying to understand how to correctly use the callback version of Meteor.subscribe
with React hooks. Below are two implementations of similar functionality. The first one follows the standard approach and the second one tries to get more fine-grained updates from Meteor through callbacks for the Meteor.subscribe
and Collection.find
methods. The first one works fine. The second one has some hiccups, for instance when loading a page for the first time results in an almost immediate onStop()
while navigating the app with react-router
works as expected. What am I doing wrong?
The first one
import {Meteor} from 'meteor/meteor';
import {useTracker} from 'meteor/react-meteor-data';
const makeFindOne = (Collection, subscription) => (
init,
query,
options,
deps
) => {
const loading = useTracker(() => {
const handle = Meteor.subscribe(subscription, query, options);
return !handle.ready();
}, deps);
const upToDate = useTracker(() => {
return Collection.findOne(query, options);
}, deps);
const found = Boolean(upToDate);
const fields = {...init, ...upToDate};
return {loading, found, fields};
};
export default makeFindOne;
The second one (note that here we receive the minimum amount of field updates and retain the last value of fields
if the item gets deleted).
import {Meteor} from 'meteor/meteor';
import {useState, useEffect} from 'react';
const makeCachedFindOne = (Collection, subscription) => (
init,
query,
options,
deps
) => {
console.debug({init, query, options, deps});
const [loading, setLoading] = useState(true);
const [found, setFound] = useState(false);
const [fields, setFields] = useState(init);
console.debug({loading, found, fields});
useEffect(() => {
setLoading(true);
setFound(false);
setFields(init);
let current = init;
let queryHandle;
const handle = Meteor.subscribe(subscription, query, options, {
onStop: (e) => {
console.debug('onStop()', {e});
if (queryHandle) queryHandle.stop();
},
onReady: () => {
console.debug('onReady()');
setLoading(false);
queryHandle = Collection.find(query, options).observeChanges({
added: (_id, upToDate) => {
setFound(true);
current = {...init, ...upToDate};
setFields(current);
},
changed: (_id, upToDate) => {
current = {...current, ...upToDate};
setFields(current);
},
removed: (_id) => {
setFound(false);
}
});
}
});
return () => {
console.debug('handle.stop()');
handle.stop();
};
}, deps);
return {loading, found, fields};
};
export default makeCachedFindOne;