Tracker that depends on React state

What is the more natural way to set a tracker that depends on a component state with React? Do I have to create a reactive variable that mimics its behavior or is there a better solution?

That doesn’t work

const self = this;
Tracker.autorun(() => {
      console.log(self.state.someState);
});

That works but doesn’t feel very React

const self = this;
self.someState = new ReactiveVar(this.state.someState);
Tracker.autorun(() => {
      console.log(self.someState.get());
});
...
this.setState({someState: someValue})
this.someState.set(this.state.someState)

Scroll down a little bit in the threads, we solved that problem already:

thanks that looks good. But it seems that you are focussing on bringing reactivity to what’s happening within render, which is not where one would start a tracker.

That doesn’t work

import React, {Component} from 'react';
import {TrackerReactMixin} from 'meteor/ultimatejs:tracker-react';
import reactMixin from 'react-mixin';

export default class TrackerTest extends Component {
  constructor(props) {
    super(props);
    this.state = {
      SomeUIState: false,
    }
  }
  render() {
    console.log('from render', this.state.SomeUIState);
    return <div></div>
  }
  componentWillMount() {
    const self = this;
    Tracker.autorun(() => {
      console.log('SomeUIState', self.state.SomeUIState);
    });
  }
  componentDidMount() {
    const self = this;
    // change the state after some time
    setTimeout(() => {
      self.setState({'SomeUIState': true})
      console.log('UI State has changed to true')
    }, 1000);
  }
}
reactMixin(TrackerTest.prototype, TrackerReactMixin);

I don’t understand what you try to do here. Just use a reactive data source, no need to use an additional autorun. Also, constructor() and componentWillMount() are exactly the same thing in react ES6. Take a look at the TrackerReact-Example App

What are you trying to do within the Tracker.autorun? If it’s UI-related, just stay on the React side and use state, and render to do the update. If it’s more app- or data-related, stay on the Meteor side and try to use ReactiveVars and getMeteorData.

If the change is coming from outside the component as a result of user action, then you should bring in a Flux store of some sort to power variables in getMeteorData. You could use Tracker in such a case, but I tend not to use Tracker in React. Tracker autoruns can run anywhere at anytime and it’s easy to lose “track” (ha!) of them, resulting in autoruns running when you least expect it and vice-versa.

1 Like

Good to know for constructor and componentWillMount. thanks.

I’m not using the tracker as a data source, but to start an action when some state is met & some data is available, and to do it only for the first time that this condition is met (so render is not the good place to do that.).

I’ll probably use a mixture of a tracker that’ll react to the data change and of a vanilla callback when the state is changed.

Since state triggers reactivity in React, if would feel natural for it to trigger reactivity in Meteor as well and I was wondering if there’d be something that would react both worlds.

Like render reacts to state change, and Tracker reacts to Meteor’s data change, but there’s not something that react to either.

I’m trying to react to both UI and data related things :slight_smile:

I am still not sure what you try to do. You can not stuff something arbitrary into tracker and expect it to become reactive. Tracker works with tracker aware libraries; though it is possible to write costum handlers. Read up on it here:
https://atmospherejs.com/meteor/tracker

If you try to communicate between react and blaze, you could simply use TrackerReact and use a session variable. That you can change inside react or blaze.
https://www.meteor.com/tutorials/blaze/temporary-ui-state
Session.get(“hideCompleted”)
Than

import React, {Component} from 'react';
import {TrackerReactMixin} from 'meteor/ultimatejs:tracker-react';
import reactMixin from 'react-mixin';

export default class TrackerTest extends Component {
  constructor(props) {
    super(props);
    this.state = {
      SomeUIState: Session.get("hideCompleted"),
    }
  }
  render() {
    console.log('from render', this.state.SomeUIState);
    return <div>{Session.get("WelcomeToMeteor")}</div>
  }
// (...)

I’m trying to do something like this:

import React, {Component} from 'react';

class Test extends Component {
  constructor(props) {
    super(props);
    this.state = {
      userHasClicked: false;
    };
    this.handle = subscribe('Item', props._id);
  }
  render() {
    return <button onClick={this.onclick.bind(this)}>
      click me
    </button>
  }
  onclick() {
    this.setState({userHasClicked: true})
  }
  componentDidMount() {
    const self = this;
    this.computation = Tracker.autorun() {
      if (self.state.userHasClicked && self.handle.ready()) {
        someCallback();
        self.computation.stop();
        self.computation = null;
      }
    }
  }
  componentWillUnmount() {
    this.computation && this.computation.stop();
    this.computation = null;
  }
}

And currently I’m doing this to make to make it work

  onclick() {
    this.setState({userHasClicked: true});
    if (this.handle.ready())
      someCallback();
  }
  componentDidMount() {
    const self = this;
    this.computation = Tracker.autorun() {
      if (self.handle.ready()) {
        if (self.state.userHasClicked)
          someCallback();
        self.computation.stop();
        self.computation = null;
      }
    }
  }

which is ok but duplicates logic and will get more complex when more variables get involved. So I was curious if there’d be a way to combine the UI and Meteor reactivity in one place.

Can you just use all Tracker vars instead? Something like this (adapted from your snippet, but not tested):

import React, {Component} from 'react';

class Test extends Component {
  constructor(props) {
    super(props);
    this.userHasClicked = new ReactiveVar(false);
    this.handle = subscribe('Item', props._id);
  }
  render() {
    return <button onClick={this.onclick.bind(this)}>
      click me
    </button>
  }
  onclick() {
    this.userHasClicked.set(true);
  }
  componentDidMount() {
    const self = this;
    this.computation = Tracker.autorun() {
      if (self.userHasClicked.get() && self.handle.ready()) {
        someCallback();
        self.computation.stop();
        self.computation = null;
      }
    }
  }
  componentWillUnmount() {
    this.computation && this.computation.stop();
    this.computation = null;
  }
}

I suppose the component would not re-render, in this case, but then you could keep both the ReactiveVar and the state variable.

If that doesn’t work, have a look at mobx: http://mobxjs.github.io/mobx/index.html

1 Like

Yup that’d work

This still doesn’t feel super right to me from a design standpoint because you have two reactive layers that need to be handled separately, and here you duplicate reactive variables and state. It’d be nice to have Meteor & React integrated in a way that you don’t have to think of them as two things, as it is with Blaze.

Thanks for your time!