Closed yched closed 5 years ago
@yched: Thank you for submitting a pull request! Before we can merge it, you'll need to sign the Meteor Contributor Agreement here: https://contribute.meteor.com/
Hi there, just now as I submitted my version of react meteor hooks (#263) I saw yours!
I separated two hooks one for subscription and one for data. I would love to get some feedback on your and mine solution. How do you handle stopping a subscription when a component is unmounted? As far as I understand, the returned function of useEffect
does the cleanup. I had to separate the hook functions to have access to the handle
in the return function and to return results from find().fetch()
/ findOne()
.
Your solution for the first render looks promising. Maybe one could do the same but cache the handles in an array to stop all subscriptions in a useTracker
function.
Cheers :+1:
@leoc
I separated two hooks one for subscription and one for data
I think offering various focused hooks like useSubscription(name, ...args), useDocument(collection, selector, options), useReactiveVar(var)... could be worthwhile, especially as it encourages splitting into granular reactive functions that only rerun when their dep gets invalidated, rather than a single big one which reruns as a whole when any of its deps gets invalidated.
But as I outlined in the OP, I do see a lot of value in providing a useTracker hook that acts like the current withTracker HOC does, i.e. accepting a function that can mix subscriptions and data : it lets us reimplement withTracker on top of it (as the PR does), which instantly makes all existing Meteor/React apps compatible with React's soon-to-be-released "concurrent mode" (Fiber, Async, Suspense, the name has changed a few times), Otherwise, you have to rewrite your whole app to use "functional components + the new hooks" instead of "classes + withTracker" to be compatible, which would be a huge pain.
Then the more specialized ones (useSubscription, useDocument, useReactiveVar...), are just a couple one-line wrappers around the generic useTracker.
(Also, hooks can't be inside if()s, meaning a hook like useTracker(reactiveFunc)
is the only way to be able to conditionally subscribe to a publication - you write the condition in the function. useSubscription(name, ...args)
always subscribes)
How do you handle stopping a subscription when a component is unmounted? As far as I understand, the returned function of useEffect does the cleanup.
Yes, stopping the reactive computation on cleanup automatically stops the subscriptions that it made.
Maybe one could do the same but cache the handles in an array to stop all subscriptions in a useTracker function.
Yep, I have written just that actually, didn't find the time to push yet :-) Will try do so soon.
So yes, the challenge here is :
We need to run the passed reactiveFn
immediately on mount so we can set the result as our initial state and return it to the component for its first render.
Yet mount time is too early to trigger side effects, they should only happen in useEffect(), i.e at didMount / didUpdate time (in concurrent mode, React can start mounting a component and then ditch it to render a higher priority update first, and restart mounting our component later) So we don't want to actually run subscriptions yet (in the scenario above, we'd have no way to stop the subscriptions made during an aborted mount, and they would just induly persist). Subscriptions should only happen within useEffect at didMount / didUpdate time.
The PR so far runs reactiveFn
once on mount without subscriptions (by temporarily replacing Meteor.subscribe with a stub), and once right after that onDidMount (with full reactivity and subscriptions).
Ideally we'd want to run it just once on mount, and then only reactively when one of its deps gets invalidated (or if the dependencies used inside the function change and the )
Last commit solves that by having the Meteor.subscribe stub collect the subscriptions attempted at mount time. We then actually do those subscriptions manually in useEffect (and manually register them to stop when the Tracker.autorun computation is stopped). When the dependencies change, useEffect recreates a new Tracker.autorun computation and no special handling of subscriptions is needed there.
Adjusted the "defer subscriptions from mount time to didMount time" part :
Those manual subscriptions at didMount time need to be stopped not when the computation is stopped, but on the first time it's invalidated - since the next rerun will redo the subscriptions "as normal".
Current drawback : Meteor usually optimises the "unsubscribe on invalidate, resubscribe to the same subscription on rerun" away, and is smart enough to leave the existing subscription untouched. My manual "subscribe on didMount / stop on next invalidate" escapes that, and sends the actual DDP unsubscribe / subscribe to the server. --> Need to see if there's a way to trigger Meteor's usual optimisation here.
In short, I guess I need feedback from Tracker / DDP experts here :-)
Well, come to think of it: the thing about "not firing Meteor.subscribe() at mount time because we wouldn't be able to stop the subscriptions if the mount was cancelled / restarted later by React concurrent mode", also applies to Tracker.autorun computations : we wouldn't be able to stop them if the mount is canceled.
A Tracker computation is a kind of subscription (the general notion of subscription, not Meteor.subscribe specifically) : something that you setup at some point and need to stop/cleanup to avoid memory leaks. React concurrent mode says "no subscription at mount time, only at didMoiunt / didUpdate with useEffect()", that applies to Tracker.autorun too.
So I guess the approach before the last two commits was the right one :
AFAICT, that's also the approach other libraries that do "return the results of a function and update them over time as they change" (like react-redux with its connect(mapStateToProps(state, props))
for instance) are taking : run once on mount, then rerun and setup change tracking on didMount. The difference is that we additionally need to silence Meteor subscriptions made in the first run.
Reverting to 46c243a, then. Simpler code, less awkward messing around with subscriptions...
Whoever is in charge of this project - what are your thoughts on this method? I really love the syntax and am wondering if we would be better off spinning of a new package for this so that we can take advantage of it now (as opposed to just doing it as a “polyfill” duke in each project)?
Tl; dr - package maintainers should chime in so we can stop wasting time on this pull request and have someone step up to create a new package.
To be clear, I believe this is the right spot for the code to live (with documentation on meteor guide already). Maybe we create a package that can be the standin until hooks are out of RFC stage?
Side note : I'll be away for the next two weeks and won't be working on this in the meantime. As far as I can tell, the current code works fine and looks stable to me.
Still TBD : how exactly the package should deal with "stick with current implementation for React < [the 1st React version that officially ships hooks], use the hook-based implementation otherwise". Maybe just a new major release that requires a minimal React version ?
But yeah, on that question and on the code itself, feedback from the package maintainers is probably what's needed now.
@jchristman : Yes, ideally this (or something like this) would simply be the next version of react-meteor-data.
As for creating an actual, temporary/experimental package with the code from this PR so that people can actually start using it : why not I guess - at the moment I have no plans of doing that myself, but I'm perfectly fine with someone else taking the code and publishing a package though ;-)
It should IMO just be very clear that it is a temporary package until react-meteor-data provides somithing similar, and that the code hasn't been vetted yet by the authors of the current React integration ?
This looks really awesome! my only worry is createContainer/withTracker backwards compatibility. Are there any strong reasons why we can't leave them as is?
@dburles: see above : the big gain is instantly making all existing apps compatible with React Suspense, by moving away from componentWillMount / componentWillUpdate (fixing #256, #252, #242, #261)
FWIW, our app is pretty withTracker-intensive, and seems to work just fine with the drop-in hook-based reimplementation in this PR.
@dburles, there’s also no reason that we couldn’t make a documentation note in README that says React <16 use react-meteor-data@0.2.16?
Okay I agree, I think it's worth it. A note in the readme is also a good idea. @hwillson any thoughts here?
I like it! We should do some additional testing though, so when this PR is ready to be merged, we'll cut a beta for people to try out. @yched Let us know when you think this PR is ready for review. Thanks!
@hwillson @dburles cool !
I think the PR is ready for review at the moment. You can read the comments above for details about another implementation I tried and abandoned (in short : React Suspense forbids Tracker.autorun / Meteor.subscribe at mount time)
Still TBD : how exactly should the package deal with "stick with current implementation for React < 16.[the 1st version that officially ships hooks], use the hook-based implementation otherwise". Maybe just a new major release that requires a minimal React version ?
Merge! :)
Imho the benefits of this PR in conjunction with React 16.8
outweigh the urge to stay backward compatible. I just submitted https://github.com/meteor/react-packages/pull/266 which also relies on a newer React version (16.3
). I'm happy to refactor that PR once this has been merged.
Also once generally approved https://github.com/meteor/react-packages/pull/262/files#diff-f54656ab7cd59d21708df27a3c521da5R21 should be solved ;)
@dburles @hwillson : any feedback ? React 16.8 has shipped with hooks :-)
Just going to summarise what I think should be the plan:
@dburles : works for me.
I'll try to do that in the next couple days
Any updates on this? Are there any contributions required that I could add? @yched if you need help with the documentation, I would happily contribute.
Would it make sense to make this a separate package - maybe called react-meteor-hooks
(or meteor-react-hooks
?). It's an opportunity to remove some of the cruft from that older package, including exposing the goods using the lazy module flag.
The changes could even be back ported to react-meteor-data
eventually (but not necessarily right now) by updating the old package to use the new package, and replacing withTracker
.
What do you think?
Also, if anyone is interested, I wrote a couple of super small hooks on top of this - I'm really digging hooks! Feels like Meteor again.
const useSubscription = (name, ...rest) => useTracker(
() => Meteor.subscribe(name, ...rest).ready(),
[name, ...rest]
)
/// ... used elsewhere
const isReady = useSubscription('my-pub', 'arg1', 'arg2')
And I always wanted an easy way to persist data through HCP, and hooks finally allow that!
import { Session } from 'meteor/session'
export const useSession = (name, defaultValue) => [
useTracker(() => {
// Session.setDefault prevents resetting the value after hot code push
Session.setDefault(name, defaultValue)
return Session.get(name)
}, [name, defaultValue]),
(val) => Session.set(name, val)
]
// ... used simply:
const [myVar, setMyVar] = useSession('mySessionVar', 'initial-value')
(I settled on using Session for for a bunch of reason I explained in the forums.)
Are you related to Benjamin? Because this idea is fire. I totally think it makes sense. On Mar 21, 2019, 3:44 PM -0400, Kevin Newman notifications@github.com, wrote:
Would it make sense to make this a separate package - maybe called react-meteor-hooks. It's an opportunity to remove some of the cruft from that older package, including exposing the goods using the lazy module flag. The changes could even be back ported to react-meteor-data eventually (but not necessarily right now) by updating the old package to use the new package, and replacing withTracker. What do you think? Also, if anyone is interested, I wrote a couple of super small hooks on top of this - I'm really digging hooks! Feels like Meteor again. const useSubscription = (name, ...rest) => useTracker( () => Meteor.subscribe(name, ...rest).ready(), [name, ...rest] )
/// ... used elsewhere const isReady = useSubscription('my-pub', 'arg1', 'arg2') And I always wanted an easy way to persist data through HCP, and hooks finally allow that! import { Session } from 'meteor/session'
export const useSession = (name, defaultValue) => [ useTracker(() => { // Session.setDefault prevents resetting the value after hot code push Session.setDefault(name, defaultValue)
return Session.get(name) }, [name, defaultValue]), (val) => Session.set(name, val) ]
// ... used simply: const [myVar, setMyVar] = useSession('mySessionVar', 'initial-value') (I settled on using Session for for a bunch of reason I explained in the forums.) — You are receiving this because you commented. Reply to this email directly, view it on GitHub, or mute the thread.
@mattblackdev Ha! No, but I'll take that as a compliment!
Another note, PR #263 has thinking along the lines of what I've suggested, with a slightly different implementation. I think it makes sense to have a core useTracker
as defined in this PR, with various other implementations built on top of that.
Ha! I love these helper hooks. I see why adding one basic useTracker
hook makes sense.
I just wanted to release my work so far as react-meteor-hooks
package but you/someone beat me to it.
https://www.npmjs.com/package/react-meteor-hooks
Looks good! :+1:
@leoc If you release that as an Atmosphere package we could reduce the overhead of the packager boilerplate in the modern bundle. (It'd be nice if there was a way to achieve the same from npm packages.)
I think all we're missing is the documentation. @yched have you begun work on that? If not, @holmrenser mentioned he could take it up.
Sorry about that, been prettty busy for the past weeks. Will give it a try in the very next couple days.
It seems the next minor react version is going to trigger deprecation warnings about the deprecated componentWillXxx() lifecycles the withTracker HOC is currently using, so we might want to hurry a bit getting rid of those :-/
It seems the next minor react version is going to trigger deprecation warnings about the deprecated componentWillXxx() lifecycles
Forgot to include the link : https://github.com/facebook/react/pull/15186
useTracker.js
: code/comments cleanupuseTracker.js
: added [] (empty array) as default value for useTracker
's deps
argument, to avoid infinte rerender loop in case the arg is omittedREADME.md
: documented useTracker
(and adjusted the existing withTracker
doc for easier comparison of the two approaches)I think we should be ready here - proofreading is more than welcome for the docs ;-)
Also, @dburles : do you think we still need to keep the last part about createContainer ? (do you think we still need to export createContainer and the whole of ReactMeteorData, for that matter ?)
In another recent PR (#268), the version check was removed from this package, a decision I agree with.
@dburles is that what you had in mind ?
@yched looks perfect!
Pu-blish! Pu-blish! Pu-blish!
Edit: I’m a professional lurker and have been watching this for months, completely lacking the time to contribute. I’m not ashamed. 😁
@hwillson I think we are ready to publish a beta release. I left a comment earlier about how I think we should do so:
Prior to releasing this (for both beta and production). We should first publish the current package as version 1.0.0 and then release this as version 2.0.0, since the package currently is released as development versions 0.x.x.
I'll leave it to you to merge and publish as I don't have publish permissions on Atmosphere.
@yched Was there anything outstanding in terms of other PR's we should merge for this release? I wrote earlier that #266 would be good to include.
Edit: Sorry @yched I must have overlooked your comment on that PR, I'll have to spend some time on the details of what it entails, but I am happy to go with your best judgement on it.
@dburles : In terms of other PRs :
yep, mostly #266 (agreed that v2 = this PR here + #266). IMO it could make sense to merge #266 before this one here, since as-is this PR causes errors on code having a ref on a withTracker-decorated component. By committing #266 first and then rebasing this one on it, we ensure the commit history contains no "broken" step ?
also related is #268, which was willing to get rid of the checkNpmVersions() check for React (and was approved by @benjamn), while I'm bumping it here :-) TBH, I have no strong opinion on that one, just not sure how else to communicate "v2 requires a higher version than v0/v1" (other than by a passing mention in the README I mean)
To replace checkNpmVersions, we could put a simple runtime check for the react version - it'd be much smaller than 10K to do so:
if (Meteor.isDevelopment ) {
// TODO: Here we can even kick a warning saying react is not installed, if it's not detected
const v = React.version.split('.')
if (v[0] < 16 || v[1] < 8) {
console.warn('react-meteor-data 2.0 requires React version >= 16.8, etc.')
}
}
If there is a way to only include this warning in a Development bundle in Meteor's package system, that'd be even better.
@CaptainN : yup, that would work. @dburles : where do you think the removal of checkNpmVersions() should happen ? v2 or v1 ? And if v2, do you think such a change belongs to this PR or rather a separate one ?
FWIW, I've been willing to move the checks about reactive funcs returning a Mongo.Cursor to Meteor.isDevelopment as well, but likewise, that is not strictly related to this PR here ?
In short : we sort of know where we want to go, it's more a matter of orchestration now ;-) I'm a bit wary that cramming to much in a single PR might get in the way of actually merging it - but I'll go whichever way you see fit.
We could publish the removal of checkNpmVersions in v1.1.0, but I don't think it's worth the effort since we want to retire 1.0 anyway. I'm happy for these enhancements to be included in this PR, since I wouldn't consider them major features by themselves.
Alright, so :
This is now an all-inclusive PR :-)
FYI: If you can't wait for this PR to pulled, and you need to get started with hooks, here's an alternative while we wait: https://github.com/andruschka/react-meteor-hooks
Bump - any news ? :-)
@yched i found a bug:
given: const currentUser = useTracker(() => Meteor.user())
if you logout on develop, you'll get:
TypeError: Cannot convert undefined or null to object
because of checkCursor
which will break if the passed argument is null
@macrozone Nice catch - typeof null === 'object' :-/ Should be fixed now.
Hey @benjamn @abernix, just drawing some attention to this PR here. It looks like it's ready to go. We just need someone with the capability of publishing to atmosphere to get this sorted.
The general plan is (as I had described earlier):
Prior to releasing this (for both beta and production). We should first publish the current package as version 1.0.0 and then release this PR as version 2.0.0, since the package currently is released as development versions 0.x.x.
We should release this as version 2.0.0 since it includes breaking changes. @hwillson also mentioned first publishing a beta version for testing, but I don't mind either way. I believe @yched has been using it in production for some time.
I’ve also been using this in production as a local file import instead of a node_modules, through the power of copy/pasta. I’d certainly prefer to have only one package, since I till use withTracker for more complex Tracker related things...
bump @dburles @benjamn @abernix ?
👋 @abernix @benjamn @hwillson just drawing attention here again as this release is at the mercy of anyone who has the rights to publish under the mdg
org on atmosphere. There's been a lot of time and effort put in by the community on this feature, it would be great to get it out there.
As initially posted on forums.meteor, this is a first attempt at providing a useTracker hook to get reactive data in functionnal React components.
Example usage :
The PR also re-implements the existing withTracker HOC on top of it, which:
A couple questions remain open (more details in comments below, to follow soon), but this already seems to work fine in our real-world app heavily based on withTracker.