Principals of writing Meteor + React (with hooks) App

Hi guys,

I’m writing a Meter + React App and I’m trying to use latest tech as React Hooks, useTracker (instead of withTracker), useMethod, etc. I have come to the point where most of my components are using Meter.userId and some User extended properties individually.

This is not ideal, IMHO, and I’m thinking of using central store, with help of React Context and providers.

In this store I want to save in state things like Meteor.status, userId, etc, but I’m not sure where is the best place to initialise them. And should React Router wait for meteor loading before process the routes?

Is there are 2020 Manual how to correctly build Meteor + React (with hooks) App and best practices? I would hate to rewrite the app when I learn something not done to the standards.

Thanks.

1 Like

what about just creating some hooks?

const useUserId = () => useTracker(() => Meteor.userId(), [])

const useStatus = () => useTracker(() => Meteor.status(), [])

etc.

2 Likes

This will work, thanks, but I’m more interested to hear the general approach in building such Apps and option to use central store for state variables.

In my current app state I see too many components re-renders as each of the component uses its own useTracker to access Users data.

but they do actually need to rerender, when the user data change, don’t they?

They do, but not 3-4 times, anyway central store will not probably resolve re-render, it is just to logically organise the data.

Meteor.userId(), Meteor collections, etc. are already organizing your data, if you add another layer, you just make your app more complex.

so you should better identify uneeded rerenders and check why these occur.

its clear, that if you useTracker(() => Meteor.user()) in multiple components, that each of these components (and its children) rerender when the user data changes (or user logs out or in)

Where there’s a React, there’s a Redux. I’d do Redux any single time. Haven’t found something easier, better, more debugable than Redux. For your hooks: https://react-redux.js.org/next/api/hooks

or easy-peasy, which works on top of redux: https://github.com/ctrlplusb/easy-peasy

but i advice strongly against mirroring stuff like Meteor.user, that already lives in a minimongo collection into redux. use redux only for local state

1 Like

Redux was something I considered from the beginning, but with latest React additions and growing use-cases of using Hooks made me decide to stick with pure React. There are many articles which backing the idea of using pure React with Hooks for small project as oppose to React+Redux

Thanks for the links I’ll have a look anyway.

One way another, with Redux or not, should I really store Meteor.userId, Meteor.status in the Store/state, or use it directly anywhere I need, what are the pros/cons of each approach?

But there are always extended user properties, like Name, Email, etc which I cannot use directly with Meteor. method and have to use subscription, these I assume, make sense to store in Redux or React store, right? Thanks.

I have to really disagree with this:

Where there’s a React, there’s a Redux.

And it’s not only me, the founder of Redux and the lead of React also says that a lot of the time you probably don’t need Redux: https://medium.com/@dan_abramov/you-might-not-need-redux-be46360cf367

This is especially true in the context of Meteor, which provide a lot of the client side global helpers that diminish the need for a state container.

IMHO, redux is extremely bloated as well.

3 Likes

Really good topic to bring up @dens14345 thanks! I’m also curious how others resolve these…

I’ve previously used Tracker for all pages but then I realise most pages don’t need reactivity so I started turning them into a functional components that by useEffect, get page data on component mount and that’s it. I have made this a pattern and only use the reactivity with Tracker when/if needed. I think this saves a lot of CPU as well as it makes the code cleaner and simpler.

Then for the userInfo, I use Context API that subscribes to the Provider rather than always calling Meteor.user() (which is also just fine I believe)…

Thanks,

I agree about redux, it may become useful as project grows, but at the moment React offers enough functionality.

So far I created a central store (with reducer help), and trying to figure what is to store there and what is not, it makes sense, to me, to store reusable data, like Meteor.user(), etc.

I’m also trying to figure how to reduce number of data requests during multiply components re-renders.
A lot of data I use is received with Methods, these I don’t want to re-request, and thinking of storing them in store. Its really messy now in my head and I’m trying to organise and make sense of it.

I’m also closely following this topic: A crazy idea for Meteor React hooks - and Suspense which is also very important for me understand.

With more and more people using Meteor + React stack I’m expecting a lot more questions like that in the future.

I tend to use @macrozone’s suggestion, and keep my reusable hooks (which is really all of them) in /imports/api/connectors/. For expensive queries and computations, you can create a Provider, and have your “connector” hooks use the context API to pull from a single Provider. Your Provider would use useTracker and your consumable hook would just access the context API. One of the real nice things about defining reusable hooks this way is that you are making very clear contracts for the consumer of the hook, and they really don’t have to worry about the implementation details.

The linked thread is talking about using a Provider for a different purpose, which should cut down on additional computation runs in concurrency mode, but probably won’t cut down on rerendering much.

I agree with what others have said here - Meteor.user() etc. are already global state storage/managed, so there’s no need to put that in something like redux. For a while it was trendy to stuff nearly everything in redux, but it turns out to be a nightmare in practice. YAGNI. But it’s still a great tool for certain types of app state.

I will say though that useTracker is missing a feature that could help cut down on the re-renders. One idea we had when we implemented useTracker was to create a way to specify a shouldUpdate method, so that you could intentionally cut down on rerenders. This becomes useful in cases where you are querying for like 2 properties and only need to update your views when one of those two properties change. Right now useTracker will update the view whenever ANY property on the document changes, even if you aren’t selecting those (that’s just how Mongo/MiniMongo works).

I’ll probably try to get that in with the Suspense work.

2 Likes

Thanks @captainn for your answer.

To summarise:

  1. Use Meteor.user() as is, without storing it in a global state.

  2. Any other user properties (like First Name, Last Name, avatar, etc) not available with Meteor.user() store in Global State, by means outlined in #3

  3. Implement reusable hooks with Provider and Context API for expensive computations

I’m still struggling with 2 things, let’s assume I have 2 components with react router:

/comp1 and /comp2

My understating, when the app is loaded, all the components loaded also: all subscriptions initialized, methods called, so we have all the data in mini-mongo

Immediately after loading, the app is at /comp1. When I click on the link to route to /comp2, it does following:

  • initialising all the subscriptions again

  • calling all the methods again

  • rendering and re-rendering component as many times as it sees fit

So it’s kind of a loop: UI state changed (user click), this triggers component re-render (fair enough), component re-render triggers useTracker subscriptions and methods called. I call methods in useEffect hook, and have correctly assigned dependencies.

I assume there are a lot of unnecessary re-renders, computations in my code. Coming from the PHP world I’m still thinking in the ‘server side’ way and struggling to understand how the things work behind the scene, which is important to build an efficient, fast and reliable app.

Everything you wrote looks fine. Rerenders are designed to be cheap, and even disposable in React (in fact, concurrent rendering mode depends on the disposability of “stateless” functional components). You should only worry about it if it slows everything down.

One thing you might need to wrap your head around with useTracker, is that it will run and rerun for it’s entire lifecycle - which is probably a newer concept coming from PHP. It does things when the component is mounted, like starting a subscription, which will update the computation whenever anything it subscribed to changes, including loading and individual document change events, and that will cause rerender. That’s probably what you want.

A react rerender will not rerun everything in the computation from scratch every time (it might rerun the Mongo query though, if you don’t use deps), so you don’t have to worry about it restarting the subscription on every re-render. Subscriptions are only started on mount - at the beginning of the lifecycle. When the component is unmounted (you navigate to another route, with different components, etc.), then the computation is destroyed, and subscriptions stopped, etc.

One thing that wasn’t clear in your writeup though - you should NOT call methods from useTracker. That would cause the method to be invoked every time the computation is run, which can happen for a whole bunch of reasons. useEffect with deps is the right place to call a computation. One interesting pattern you can use, is you can use useEffect to call a method, then update a local memory only collection (or use ground:db), and in the same component, if you have useTracker set up to query that collection, it’ll update the react component like magic! (Or you could just use local react state, the meteor way would get you a global data store that can be used in multiple component instances.)

1 Like

Hi,

I’m not calling Method from within useTracker, I have implemented something similar to https://github.com/andruschka/react-meteor-hooks/blob/master/hooks/useMethod.js
and executing useMethod from component root, something like:

const users = useMethod('users.findByUserId');

then inside useEffect:

  useEffect(() => {
    if (users.data) {
      setUsersData(users.data);
    }
    return () => {
    };
  }, [users.data])

I’m using state to hold users data, which I’m not sure right, as it is already in: users.data

And as per my previous post, I created a React store similar way explained in https://blog.logrocket.com/use-hooks-and-context-not-react-and-redux/

I’m storing, so far, extra user data, which is required in most of the components down the tree, but I’m not storing data from Methods to global store as it makes no sense, they only required in a particular component.

This is so far best I can come up with, by using many articles, GitHub examples, etc. There is no single pleace which explains how to build Meteor + React Hooks app according to best practices, that is why I started this topic in hope to find or make one.

1 Like

Another fantastic library I’d consider is react-query: https://github.com/tannerlinsley/react-query. It solves server data querying, by properly separating server-data (your DB) from frontend-data (routing, temporary component state, etc.), which should never have been mixed together (redux introduced that, sadly).

Here’s the video that sold me on it, well worth the watch: https://www.youtube.com/watch?v=seU46c6Jz7E

We’ve essentially been building the same library in-house until we found that this library solved the same problems in a better way.

You can almost completely stop using meteor subscriptions, and create all of your static meteor method queries that refetch after certain meteor method calls.

And for the few places where you need true reactive data, create a few hooks that use useTracker as suggested above.

5 Likes

I use it directly anywhere I need, but enclosing them in useTracker for reactivity; after reading tons of articles, I find it best suits our needs.

Using React hooks in Meteor is still work in progress I must say and it improved every week with new Meteor-hooks released every now and then. I bet you, we will see very soon: useMethod, useSubscription, useUser, useUserId, etc hooks, it just makes sense. With React Suspense already available and Concurrent mode on horizon Meteor should adapt.

Thanks for the tip Florian, this is looking pretty good indeed.