Handle publication errors in withTracker?

I have a HOC BoardLayoutContainer that subscribes to a board. I pass the loading state as well, so I know if loading === false && !board => to show a BoardNotFound page.

So far so good.

Now, when the board ID does exist, but the user doesn’t have permission to view it, I want to show another error. I tried to achieve this by using this.error in the publication, and implementing an onStop in the HOC:

export default BoardLayoutContainer = withTracker( ({ match, history }) => {
  const boardId = match.params.id
  const { error, setError } = useState()

  const onStop = e => setError( e.message() || 'Error loading board' )

  const bs = Meteor.subscribe( 'board', boardId, { onStop } )
  const board = Board.first({ _id: boardId })

  return { loading: !bs.ready(), error, board }
})( BoardLayout )

However, this doesn’t work:

Uncaught Invariant Violation: Invalid hook call. Hooks can only be called inside of the body of a function component.

Since the error is async, and there is no function for it on the handle returned by Meteor.subscribe, I am at a loss how I should do this… Hopefully you have some ideas.

Solved it with a ReactiveVar (note that the var needs to be declared outside the withTracker function otherwise it will trigger an endless loop).

This is an example that would have been fixed by the work in our PR to migrate to a hook.

Great. Is there an easier way in your implementation to check for subscription errors as well? I find the whole onStop + check for error argument very tedious, and it feels clunky that it’s totally different from the ready() check.

BTW, you may not want to define that ReactiveVar outside of your component, as it could leak memory depending on how you define it, especially if you do SSR.

If you use a ReactiveDict you should be able to specify a default value, though you’ll need to make sure the name value is globally unique (at least for the current state of the react tree).

1 Like

There probably isn’t an easier way to check for errors - however, the error you posted about was from React, not from Meteor - it was saying that you tried to use a hook outside of a functional component - which was true. In the PR, the thing that changed is that the function runs as part of a functional component, so it would have been safe to use the hook the way you did.

No, what I originally was trying to do is handle an error thrown using this.error in a publication. I think this is a very elegant way to check for permissions; only bummer is the clunky client side API (also for example that .ready() returns false when there is an error). Ideally I wouldn’t need state/ReactiveVar at all; and I could just access the subscription result as a promise/via handles like .done(), .error(), etc.

I’ve been following your comments, and it seems you had quite a difficult time to get the hook to work properly with the React hooks philosophy, and had to resort to some hacks to get around the limitations, right? I haven’t checked the code yet but am curious how you feel about the result now, do you think it is (and will be) stable enough to put in production?

Its not hacks really, it’s just not ideal (I may have called it hacky elsewhere). I have it in production now, and I believe a few others do as well. It should be stable and not leak memory, etc. The only thing I’m not certain about is how it’ll work in concurrent mode, but that’s not stable yet anyway. It should be fine with handled error boundaries and suspense.

The reason I call it hacky is that we had to resort to abusing useMemo for it’s deps compare functionality and use some timeouts in order to make sure we clean up in cases where a render will never be committed. Honestly, we shouldn’t have had to work so hard, but the react team insists they can’t provide the tools to make this easier because that would make some of their efforts less effective. I disagree for a lot of reasons, but this is what they believe.

I have thought of throwing in the towel and doing it the pure react way (even made a brief attempt), where we simply accept double render on mount (and double subscribe - unless we use hacks), everywhere the hook is used, but that’s worse in so many ways, and besides our solution is already working.

If the Meteor team would like to do more pure implementation, I’d probably go back to something very similar to @yched and @menelik’s original approach, with some minor updates (basically still begrudgingly monkey patch Meteor.subscribe, but run the first reactive function in a computation, then immediately destroy it, then set up everything again in useEffect).

The current HOC approach doesn’t suffer these issues, right? Would you say, with your current knowledge, that a HOC is a better fit for Meteor subs?

The current HOC is temporary though. It’s using some lifecycle hooks which have been deprecated, and will at some point be removed. It’ll end up facing the same challenges at some point in the future. Our PR actually reimplements the HOC to use the hook internally.

If there was an equivalent to componentWillMount and/or some reliable way to clean up after discarded renders in hooks we could make the hook just as clean as the HOC. But the React team has not provided those tools. I do wonder whether we can eventually use react-cache (some react docs suggest this), but that is unstable right now.

1 Like