No, I don’t think avoiding Tracker alone is reason enough to use these. A computation actually has very little overhead, and useTracker
has a complex implementation, but isn’t all that inefficient either (though it does get messy with concurrent mode and all the timeout management - it’s still better than some other platform integrations).
I was after something else - I wanted to reduce the number of redraws and iterations over the results set, and then secondarily explore some options for further optimizations. This useCursor
hook returns the cursor directly, and iterates through that in React, rather than dumping everything to an array, then iterating it again in React. (I didn’t measure the performance impact of that, like I said.) I also have some ideas to maintain a immutable list using observeChanges
hook, so that all the children can be set up with memo
and reduce some reconciliation overhead. Alternatively, I was thinking of some way to use a custom iterator component, which would set up change observations per document, and avoid a lot of reconciliation (I can’t think of a viable way to make this work though that doesn’t have MORE overhead, than just using memo on an immutable list, because I’d have to maintain observers for the whole list for added
and removed
, then one each for every document on changed
- I don’t know if its worth it).
All of this could probably done with useTracker
and some extra data structures - but it’s overhead. The other benefit of these hooks though is that they are very simple compare with useTracker
. These are completely easy to read and understand. useTracker
is, less so… Sadly, useTracker
being general purpose really can’t be any simpler. These can be simpler because they are so task specific. The useSubscription
hook for example will avoid any kind of chance for creating side effects in first render in a very clear and obvious way. I think it’s similarly clear for useCursor
without any kind of hackery. These are very straight forward.
Here’s an update BTW, with some similar hooks for user APIs. I changed useSubscription
to use a factory, so I can have a clean way to specify deps
. IMHO, deps are the weak point of hooks in general…
import { Meteor } from 'meteor/meteor'
import { Mongo } from 'meteor/mongo'
import { Tracker } from 'meteor/tracker'
import { useEffect, useMemo, useReducer, useRef, useState } from 'react'
const fur = (x: number): number => x + 1
const useForceUpdate = (): (..._:any[]) => void => {
const [, forceUpdate] = useReducer(fur, 0)
return forceUpdate
}
export const useSubscription = (factory: () => Meteor.SubscriptionHandle, deps: ReadonlyArray<any> = []) => {
const forceUpdate = useForceUpdate()
const subscription = useRef<Meteor.SubscriptionHandle>({
stop() {},
ready: () => false
})
useEffect(() => {
subscription.current = factory()
const computation = Tracker.autorun(() => {
if (subscription.current.ready()) forceUpdate()
})
return () => {
computation.stop()
}
}, deps)
return subscription.current
}
export const useCursor = <T>(factory: () => Mongo.Cursor<T>, deps: ReadonlyArray<any> = []) => {
const cursor = useMemo(factory, deps)
const forceUpdate = useForceUpdate()
useEffect(() => {
const observer = cursor.observeChanges({
added: forceUpdate,
changed: forceUpdate,
removed: forceUpdate
})
return () => {
observer.stop()
}
}, [cursor, ...deps])
return cursor
}
export const useUser = () => {
const [user, setUser] = useState(() => Meteor.user())
useEffect(() => {
const computation = Tracker.autorun(() => {
setUser(Meteor.user())
})
return () => computation.stop()
}, [])
return user
}
export const useUserId = () => {
const [user, setUser] = useState(() => Meteor.userId())
useEffect(() => {
const computation = Tracker.autorun(() => {
setUser(Meteor.userId())
})
return () => computation.stop()
}, [])
return user
}