Skip to content
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 16 additions & 30 deletions packages/react-mongo/react-mongo.ts
Original file line number Diff line number Diff line change
@@ -1,62 +1,48 @@
import { Meteor } from 'meteor/meteor'
import { Mongo } from 'meteor/mongo'
import { Tracker } from 'meteor/tracker'
import { useEffect, useMemo, useReducer, useRef, DependencyList } from 'react'
import { useEffect, useMemo, useReducer, useState, DependencyList } from 'react'

const fur = (x: number): number => x + 1
const useForceUpdate = () => useReducer(fur, 0)[1]

const useSubscriptionClient = (factory: () => Meteor.SubscriptionHandle | void, deps: DependencyList= []) => {
const forceUpdate = useForceUpdate()
const { current: refs } = useRef<{
handle?: Meteor.SubscriptionHandle,
updateOnReady: boolean
}>({
handle: {
stop () {
refs.handle?.stop()
},
ready () {
refs.updateOnReady = true
return refs.handle?.ready()
}
},
updateOnReady: false
})
const useSubscriptionClient = (
name: string | false,
args: any[]
Comment thread
rijk marked this conversation as resolved.
): void | Meteor.SubscriptionHandle => {
const [subscription, setSubscription] = useState<Meteor.SubscriptionHandle>()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should put that in state : we don't need to rerender when the subscription changes
(the fact that the subscription changes will probably cause other rerenders because of minimongo results changing, but that is taken care of outside of here)

A useRef should be enough ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, that's a really good point. We don't need a rerender for a changing subscription. The ready state might change, though. I'll change it to a ref and see if I can get the ready state to invalidate immediately as well.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the ready state might change, that is handled by the ready() func being a reactive source.
So the consumer can decide to rerender (if he wants) by doing
const isReady = useTracker(ready)
with the ready function we return ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah yes, that means the stub ready() getter we initially return must be reactive too :-/
Well, that might require playing with Tracker deps to build our own reactve func wrapping the one in the subscription handler, rather than handing out that one directly

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I incorporated the changes in 76943f7.

The problem with ready(), is that it will always lag behind one render. So when changing the deps, it will still return the previous subscription's status for one render. I have a suggestion on how to fix that, I'll push that in the next commit.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok so here is a suggested fix for this problem: e37ba55. @CaptainN @yched open to better suggestions 😬


useEffect(() => {
if (!name) {
return setSubscription( null )
}

// Use Tracker.nonreactive in case we are inside a Tracker Computation.
// This can happen if someone calls `ReactDOM.render` inside a Computation.
// In that case, we want to opt out of the normal behavior of nested
// Computations, where if the outer one is invalidated or stopped,
// it stops the inner one.
const computation = Tracker.nonreactive(() => (
Tracker.autorun(() => {
refs.handle = factory()
if (!refs.handle) return
if (refs.updateOnReady && refs.handle.ready()) {
forceUpdate()
}
setSubscription( Meteor.subscribe( name, ...args ) )
})
))

return () => {
computation.stop()
}
}, deps)
return () => computation.stop()
}, [name, ...args])

return refs.handle
return subscription
Comment thread
rijk marked this conversation as resolved.
Outdated
}

const useSubscriptionServer = (): Meteor.SubscriptionHandle => ({
stop() {},
ready() { return true }
})

export const useSubscription = (factory: () => Meteor.SubscriptionHandle | void, deps: DependencyList = []) => (
export const useSubscription = (name: string | false, ...args: any[]) => (
Meteor.isServer
? useSubscriptionServer()
: useSubscriptionClient(factory, deps)
: useSubscriptionClient(name, args)
)

const useCursorClient = <T = any>(factory: () => Mongo.Cursor<T>, deps: DependencyList = []) => {
Expand Down