What is the recommended way to use Meteor.method() in React?


#1

The createContainer section in the Meteor guide


contains this statement:

Meteor operates in terms of publications and methods, used to subscribe to and modify the data in your application. To integrate the two systems, we’ve developed a react-meteor-data package which allows React components to respond to data changes via Meteor’s Tracker reactivity system.

However, it then only shows how to use pub/sub with createContainer. What is the recommended way to call a Meteor.method() and update the component reactively once the result is being received? Would you use the component’s state for this, or a ReactiveVar in combination with createContainer()?

EDIT: Even after doing some additional research on the net, I am quite confused about the best practices of data loading in React. Most articles are outdated and describe getMeteorData(), others are using React Komposer. If I understand it correctly, react-meteor-data is the recommended way to do it, but the current docs are a bit too scarce, IMHO. Is there any repo with a real app using createContainer() besides the tutorial stuff?


#2

Found some sample code in the comments of this Meteor Chef article: https://themeteorchef.com/tutorials/using-create-container

The sample code is as follows:

import {Meteor} from 'meteor/meteor';
import {ReactiveVar} from 'meteor/reactive-var';
import {createContainer} from 'meteor/react-meteor-data';
import {BookAppointmentThree} from '../components/BookAppointmentThree.js';
const openings = new ReactiveVar([]);
export default createContainer(({appointmentType}) => {
  Meteor.call('customers.coaching.openings', appointmentType, (error, response) => {
    if (error)
      console.warn(error);
    openings.set(response);
  });
  return {openings: openings.get(), appointmentType};
}, BookAppointmentThree);

The problem I see with this approach is that it would only work if the container is only used once, since the ReactiveVar is defined in the global context. If the function passed to createContainer() was a true React Component, I could use life cycle hooks to set a separate ReactiveVar for each instance. But as it is, I cannot put this initialisation code in the function, since it would be re-run reactively on each data change.

Things gets even worse if the container function would reference another reactive source. In this case, the method would be re-called again as well. At least if I understanding the docs correctly.

Maybe I am missing something quite obvious and am just too blind to see it?


#3

createContainer is only needed when you have to deal with reactive variables. Meteor methods are normally not reactive, so you can use them within your React component, f.e.:

getInfo() {
    Meteor.call('userInfo',(err,res) => this.setState({userInfo:res}))
}

If you have a reactive variable which needs to be sent to a Meteor method, you can use the lifecycle methods of React:

componentWillUpdate(nextProps, nextState)
{
     if(np.userId != this.props.userId) {
       Meteor.call('userInfo',np.userId,(err,res) => this.setState({userInfo:res}))
     }
}

You pass the userId as prop via container:

 createContainer((props) => {
      return {userId: Meteor.userId()}
 },Component);

#4

Thanks for taking your time to write this up.

The problem I have with including the data fetch code in the view component is that it breaks the separation between “smart components” and “view components”. Of course, I could move the code you wrote above to its own “smart component”, i.e. to my own custom container. But what if need both, a subscription and a method? In this case, I would have two containers in place, which isn’t nice.

I would prefer an approach where data loading from subscriptions and methods can be done in one single container. React Komposer seems to support this. But I am quite hesitating using another Arunoda package, TBH, especially if it has very old issues without any responses.

So I am still wondering what the best way would be to move the method invocation to a smart container? The guide text cited above suggests that it is possible. But unfortunately, it doesn’t explain how.

I had a look at the “pup” boilerplate from the guy behind The Meteor Chef. He’s also using a ReactiveVar, so it would only work for one container instance.


#5

Mhh okay, I guess then you have to create an extra HOC for your component. The createContainer container passes the reactive values as prop to the new HOC component which then calls the Meteor method and passes its state and its own props to your view component. So you have 3 components:

createContainer
fetchComponent
viewComponent


#6

Will you provide a code example? I’m confused. :slight_smile:


#7

Yeah. That’s what I wanted to avoid :slight_smile: But I guess it’s the only way to combine pub/sub and methods. The fetch components doesn’t have to be a HOC, though. I am using a regular Component now. What benefits would you see by wrapping this in a HOC? IMHO, these HOCs only make my head spin around too much.


#8

Here’s some sample code how this can be combined, using a regular Component instead of a HOC:

import React, {Component} from 'react';
import {Meteor} from 'meteor/meteor';
import {createContainer} from 'meteor/react-meteor-data';
import Home from './Home';

class HomeContainer extends Component {
  constructor(props) {
    super(props);
    this.state = {};
  }
  componentDidMount() {
    Meteor.call('myMethod', (error, posts) => {
      this.setState({posts});
    });
  }
  render() {
    return <Home posts={this.state.posts}/>;
  }
}

export default createContainer(({someParameter}) => {
  // pub/sub container code here
}, HomeContainer);

The class cares for the method calls, the createContainer() for pub/sub. Not nice, but works. Instead of component state, one could also use Redux or another state manager.


#9

@aadams

class ViewComponent extends Component {

    constructor(props) {
        super(props);
    }
    
    render() {
        return <div>I'm having {this.props.userId} and {this.props.userInfos}</div>
    }
}

class FetchComponent extends Component {
    
    constructor(props) {
        super(props);
        this.state = {
            userInfos: null
        };
    }
    
    fetchData() {
        Meteor.call("getUserInfos",this.props.userId,(err,res) => this.setState({userInfos:res}));
    }
    
    componentDidMount() {
        this.fetchData();
    }
    componentWillUpdate(nextProps,nextState) {
        if(nextProps.userId != this.props.userId) {
            this.fetchData();
        }
    }
    
    render() {
        return <ViewComponent {...this.props} {...this.state}/>
    }
}

const containerComponent = createContainer((props) => {
    return {userId: Meteor.userId()}
},FetchComponent);

export {containerComponent};

Oh, @waldgeist was faster :smile:.


#10

At least, he can now compare :smile:


#11

@waldgeist In some of my projects, we decided use redux not only to hold the state/data but also to be responsible for all the non-reactive data-fetching. We use redux-thunks and/or redux sagas for asynchronous calls. I like that approach bc it makes the view almost completely meteor/logic agnostic. Maybe that would be something to check out?


#12

MobX is another option worth considering for this


#13

I’ve been deep into the Meteor/React rabbit hole for a couple of years now and have found different strategies for different parts of Meteor’s data system:

If you’re not using Redux, then createContainer() from react-meteor-data is your best bet for all data loading. Just use regular subscribe calls to retrieve what you’re looking for and pass it down as props. That starts to break down once the app scales, though, your component structure starts taking weird shapes in order to accommodate where you want the data.

If you decide to jump on the Redux train to store your app’s core data, then I’ve found success with meteor-redux-middlewares. It gives you a straightforward pattern which makes it easy to load reactive data into Redux and abstracts out the details of cleaning up subscriptions. You don’t get optimistic updates, but you do get reactivity from the server.

Personally, my project only uses methods for taking actions on the server – not retrieving data. In our case, calling the method will eventually change the subscribed data, so we don’t need to worry about where the method is called. Before using Redux, it was generally in a helper method on the components using it. Now that we’ve switched to Redux, we use redux-thunk to make the call asynchronously in action creators. The simple solution is to simply dispatch an action when the call either succeeds or fails. The better solution is to have three actions for each method call: [method]_CALLED, [method]_SUCCESS, [method]_FAIL.


#14

Hey, thanks a lot for sharing these insights!