Using a REST API with React

I’ve got a file /both/lib/collections/events.jsx and in that file:

Events = new Mongo.Collection(null);

I get tons of errors in the server side console:

I20150827-10:36:15.320(-7)? Exception from sub events id rxdYDespnB6chhBrE Error: Can't publish a cursor from a collection without a name.
I20150827-10:36:15.320(-7)?     at [object Object].LocalCollection.Cursor._publishCursor (packages/minimongo/minimongo.js:243:1)
I20150827-10:36:15.320(-7)?     at [object Object]._.extend._runHandler (packages/ddp/livedata_server.js:986:1)
I20150827-10:36:15.321(-7)?     at [object Object]._.extend._startSubscription (packages/ddp/livedata_server.js:769:1)
I20150827-10:36:15.321(-7)?     at [object Object]._.extend.protocol_handlers.sub (packages/ddp/livedata_server.js:582:1)
I20150827-10:36:15.321(-7)?     at packages/ddp/livedata_server.js:546:1

I assume Meteor+React won’t do unsynchronized collections? Do I need to use Session then, to store structured data that doesn’t need to persist?

Are you publishing the collection from the server? I don’t think this has anything to do with react actually.

The error is saying you can’t publish a collection that doesn’t have a name.

Ah, shoot, you’re right. You can’t use Meteor.publish on an unsynchronized collection. I was counting on subscribing in getMeteorData and waiting for that to be ready, but I took a different approach:

Dashboard = React.createClass({
  displayName: 'Dashboard',
  mixins: [ReactMeteorData],

  getInitialState() {
    return {
      queryDone: false
    }
  },

  componentDidMount() {
    jsforce.browser.init({ /* .. secret credentials.. */});
    jsforce.browser.on('connect', (conn) => {
      let queryStr = 'SELECT ...';

      conn.query(queryStr, (err, result) => {
        if (err) {
          console.error('error', err)
        } else {
          console.log('conn.query', result);

          result.records.map(function (record) {
            Events.insert(record);
          });
          this.setState({queryDone: true});
        }
      })
    });
  },

  getMeteorData() {
    return {
      eventsList: Events.find({}).fetch()
    };
  },

  render() {
    if (!this.state.queryDone) {
      console.log('we are not ready yet');
      return <div>Loading...</div>
    }

    return (
      <EventsTable list={this.data.eventsList} />
    );
  }
});

Note that you don’t have to put something in a collection to publish it:

(that example might not actually work, it’s just an idea)

Whew. That example is a bit over my head, I’ll have to look into .added and .stop. But I also noticed you’re creating a persistent MongoDB collection, which I wanted to avoid. I assume this won’t work with an unsynchronized collection, since there’s no collection name to refer to?

Actually, that raises a question: what’s the difference between this:

Things = new Mongo.Collection(null); // on client or server

And this:

Things = new Mongo.Collection('things'); // on client only

It does totally different things on the client and server (as documented):

On the client, the name is used to get the right documents from a publication. As you can see, the added call has a collection name as the first argument, which tells DDP which collection on the client it should end up in. So passing a name for the collection on the client doesn’t technically do anything for server-side Mongo at all.

On the server, the name is used to create an actual MongoDB collection, and is used to tell publish what collection should be associated on the client. So it’s a coincidence that the name is the same on the client and server, and you could actually publish to a collection with a different name if you wanted to.

I would suggest learning about the low-level publish API with added, changed, removed, etc. It will let you do a lot more with Meteor.

Oh, that is cool. I get REST data in mimimongo client side. Nice!

I’ve actually been thinking the other way 'round. I’ve been playing with Redux and have been kind of impressed with how flux-like minimongo is (push state changes in and they flow down the template hierarchy). Got me to thinking if the client side could be implemented as a database type independent state reducer hooked to a subscription with reactive observers as async middlewares. Native queries on the server for whatever database(s) we are using, one interface on the client to read them all.

@sashko: A fix to your code above:

subscription.added("my-collection", Random.id(), item);
1 Like

Bonus points if you use an id from the API itself.

1 Like

Hmm, now this has sort of morphed into something different in my head. It would be cool to have a pub/sub structure set up for an API, such that you could change a reactive var and the publication would make an HTTP call and return a new result set.

I’ve got this code below working, but when I switch the search radius, it’s a little glitchy looking. I don’t fully understand pub/sub in the context of adding your own data manually. Not sure if I need to use .stop and when and why (and where!).

Server JS:

Meteor.publish('specificPlaces', function (radius) {
  const LAT = 40.014986, LON = -105.270546;

  let apiKey = 'Google Places API key here';
  let reqUrl = 'https://maps.googleapis.com/maps/api/place/nearbysearch/json';
  let results;

  results = HTTP.call('GET', reqUrl, {
    params: {
      key: apiKey,
      location: LAT + ',' + LON,
      radius: radius / 0.00062137 //convert to meters
    }
  });

  console.log('Made HTTP call. Radius = %d. status code:',
    radius, results.statusCode);

  results.data.results.forEach((item) => {
    this.added('places', Random.id(), _.omit(item, 'id'));
  });

  return this.ready();
});

Client JS:

Places = new Mongo.Collection('places');

Template.main.onCreated(function () {
  Session.set('radius', 25);

  Tracker.autorun(() => {
    this.subscribe('specificPlaces', Session.get('radius'));
  });
});

Template.main.events({
  'click #filter': function (event, template) {
    Session.set('radius', +template.$('#radius').val());
  }
});

Template.main.helpers({
  places() {
    return Places.find();
  }
});

Client HTML:

<template name="main">
  <div style="margin-bottom: 2em">
    Limit to radius (miles): <input id="radius">
    <button id="filter">Filter</button>
  </div>

  {{#each places}}
    {{name}}'s rating on Google is {{rating}}.<br>
  {{/each}}
</template>

PS: Sorry, this is in Blaze. I haven’t even begun to thought about how this would be structured in React!

1 Like

This looks good in principle - what do you mean by glitchy looking?

I think it looks glitchy because when I have a search with one radius, and I enter another, it adds on the new results before removing the previous results. I expected a smoother looking transition like when you use Mongo collections.

Ah. I think the best solution there would be to use the subscription ready callback to show a loading bar while the new items are being populated. Or, you can add a special key to the returned documents and filter by it on the client.

1 Like

Ahh, always the simple things!! That’s much better. Thanks, Sashko :slight_smile:

1 Like

No problem! Hope you don’t mind, I renamed the thread to reflect the topic better.

Not at all. I’m also going to post the code in React instead of Blaze, just in case others are searching for this and want to learn. After all, this is the react section! Working on it now.

Ahh, unfortunately it appears to not be possible to do a client-only Mongo collection in React when using an SSR structure, where components reside under /both. Any references to the collection from within getMeteorData() will throw an error (Places is not defined).

Any tips on how to use Meteor+React+SSR, and still have a pub/sub model using a REST API?

EDIT: I think for the mean time, since I’m on a deadline, I’m going to remove the SSR aspect and go with a strict client/server division. Unless we move to a solution where we synchronize data to a MongoDB (instead of doing REST API calls), then it’ll be easier to move back to SSR.

“Places is not defined” sounds like a file load order issue to me. Maybe put the collection declarations in a file in your app’s lib/ directory? Those files are guaranteed to load before others?

We should really improve our “structuring files in a Meteor app” story.

The issue is that I’m using SSR, and so all the React code is in the /both folder, therefore defining a client-only Mongo collection causes problems such as the above.

This is an ever-changing paradigm. Once people generally figure it out, someone says “Hey look! Meteor + React!” And then people try to figure out how to structure that one, and they eventually figure it out, then someone says “Hey look! SSR!” :grin: