Changing this.data over time


#1

I have some fairly heavy, time-dependent calculations that need to be performed a number of times. Instead of performing all of them, I only calculate the next value and cache it on this.data. But in order to keep the data fresh, I’d like to run the calculation when the data becomes invalid. Basically I’d like to run a Meteor.setInterval() call on some values of this.data and have changes picked up by render().

I’m guessing this is somewhat more complicated than just putting my Meteor.setInterval() call in getMeteorData()? I’m still at the point where React feels like magic. I’d try this myself, but my data is time-dependent and the invalidation/update cases won’t be testable for a few days–at least, unless I get another dataset, which likely isn’t easy.

Thanks.


#2

In templates, this.data is read-only and non reactive (see here). Maybe you can store your data in a ReactiveVar instead.


#3

Here’s part of my code:

var MeteorStopList = React.createClass({

  mixins: [ReactMeteorData],

  getInitialState() {
    return {limit: 10}
  },

  getMeteorData() {
    var data = {}
    var options = {}
    options.limit = this.state.limit
    var position = {}
    if(Meteor.settings && Meteor.settings.public && Meteor.settings.public.defaultPosition)
      position = Meteor.settings.public.defaultPosition
    if(Meteor.isClient)
      position = Geolocation.latLng()
    options.position = position
    data.handle = Meteor.subscribe("stops", options)
    data.ready = data.handle.ready()
    data.stops = Stops.find().fetch().map(function(stop) {
      stop.id = stop._id
      return stop
    })
    return data
  },

Each stop also contains a nextDepartures() function that calculates the next departing bus/train. This is fairly heavy since it traverses a number of GTFS tables. Probably lots of room for optimization but the data is highly normalized and I haven’t gotten to it yet. stop.nextDeparture() basically takes item 0 of nextDepartures() with a few checks to ensure that it isn’t null.

So naturally over time nextDeparture() changes and I want to reflect that in the UI, but it doesn’t happen reactively due to it requiring a number of joins. It gets even more complicated with real-time updates, which can change a departure completely outside of the stop object (I.e. stop object’s departure is rendered, then a subscription to a different data source changes the value rendered on the page.) Maybe this isn’t the best implementation but it’s a prototype for real-time rendering GTFS data with intent to improve. :smile:

With Blaze, I rendered to templates with id/class attributes then updated the specific table cells on an interval with the various changed values. With React, I might store my stops in the state, which is being passed to a child view component via props, then set up an interval function to replace the state with real-time updates as they arrive. Maybe the issue is that I read everywhere about state, and that seems like where I’d like to store my stops list, only Meteor uses this.data and I don’t see that anywhere in the official React docs. Can I mutate this.data after the fact and expect render() to pick it up? Should I shove it into state somehow? Or is this completely wrong?


#4

I don’t know about React, but in Meteor you don’t store states in this.data. You store states in ReactiveVar, ReactiveDict, local collection or Session.


#5

OK, so this.data is analogous to Blaze template data? Makes sense I suppose, but I don’t understand the data/state split. Why not have a Meteor reactive function that just keeps everything in state? I understand Reactive{Dict,Var} and friends–I’m coming from Blaze after all–but if I use and update React’s own state then it seems like it will automatically rerender/diff on change, which is what I want. The issue isn’t rerendering the component on change, it’s pushing changes to the component in the most idiomatic way. If I shove all my changes into a ReactiveVar then I’m still going to have to rerender the component, so I might as well call this.setState() and do away with a layer of indirection.

It seems like Meteor provides a lot of plumbing that duplicates stuff already offered by React. So if I’m going to prefer React to Blaze for 20% of my use, say, then why not ditch Reactive{Dict,Var} and use React for more?


#6

I think the simplest thing to do here is to trigger a rerun of getMeteorData by calling setState.

Basically the mental model is this: this.data is strictly data that comes from outside of the component, and this.state is data that the component is managing directly. But you can use this.state to change what getMeteorData does.

I think you could put a setInterval or setTimeout inside the componentDidMount callback of the component that calls setState with an incremented number to trigger a refresh.


#7

OK, so a call to setState regardless of content will cause getMeteorData to be called and force a rerender? Even setState({})?

In this instance you’re probably right, and it’s probably the best course to just reinitialize the data and trigger a rerender. I have to perform queries across 3-4 collections to calculate some of these values, and trying to make that logic reactive would be ugly and painful. :smile:
Thanks.