facebookarchive / react-meteor

React rendering for Meteor apps
948 stars 114 forks source link

onChange will not fire with delayed state changes and later added components #105

Closed sdbondi closed 8 years ago

sdbondi commented 8 years ago

A very basic and standard component which should call the change handler when the input changes. It works in my pen: http://codepen.io/sdbondi/pen/MaGovq

It doesn't in my meteor app - in fact any handler (onClick, etc) doesn't work if rendered after the initial page load - { (this.state.cond) ? <Element onChange={..}/> : ''} will also render but not fire the change.

Interestingly changes fire if I set the entries in the initial state, but with setTimeout they render but no onChange.

I've stepped through react to try and understand how events are bound (I actually got to addEventListener eventually) but it'll take a while to understand what is happening enough to debug.

export default React.createClass({
  displayName: 'VotePage',
  getInitialState() {
    return {
      entries: []
    };
  },

  handleChange(e) {
    console.log(e);
  },

  componentDidMount() {
    // this will eventually be replaced by meteor data (ReactMeteor.createClass etc)
    window.setTimeout(() => {
      this.setState({'entries': [{_id: '123', name: 'guyuy'}, {_id:234, name: 'sadfsd'}]});
    }, 1000);
  },

  render() {
    var voteEntries;

    if (this.state.entries && this.state.entries.length) {
      voteEntries = this.state.entries.map((entry) =>
        <input key={entry._id} name="entry" type="text" onChange={this.handleChange} defaultValue={entry.name}  />
      );
    } else {
    voteEntries = 'loading...';
    }

    return (
        <div>
          <h2>Vote</h2>
          <div className="island_-small">
            {voteEntries}
          </div>
        </div>
    );
  }
});
React: v0.13.0 (tried 0.13.3 too)

------------> versions excerpt 
react@0.1.13
react-meteor-data@0.1.9
react-runtime@0.13.3_7
react-runtime-dev@0.13.3_7
react-runtime-prod@0.13.3_6
reactive-dict@1.1.3
reactive-var@1.0.6
reactjs:react@0.2.4
kadira:flow-router@2.7.0
kadira:react-layout@1.4.1

----------> packages full file
meteor-base             # Packages every Meteor app needs to have
mobile-experience       # Packages for a great mobile UX
mongo                   # The database Meteor supports right now
blaze-html-templates    # Compile .html files into Meteor Blaze views
session                 # Client-side reactive dictionary for your app
tracker                 # Meteor's client-side reactive programming library

standard-minifiers      # JS/CSS minifiers run for production mode
es5-shim                # ECMAScript 5 compatibility for older browsers.

dburles:collection-helpers
aldeed:collection2
accounts-base
accounts-password
alanning:roles
# twbs:bootstrap
# fortawesome:fontawesome
wylio:mandrill
# kadira:blaze-layout
# sach:flow-db-admin
check

stevezhu:lodash
accounts-facebook
service-configuration
kadira:flow-router
universe:modules-npm
ecmascript
fixate:app-deps
universe:modules
yasaricli:slugify
maxharris9:classnames
reactjs:react
kadira:react-layout
jquery
react-meteor-data
meteorhacks:subs-manager
froatsnook commented 8 years ago

First of all, since you're using react (official from MDG), you don't need to use reactjs:react.

I just made a test app that uses your VotePage component, and the console.log is happening on change. How are you including the component?

sdbondi commented 8 years ago

Thanks, but I want to use ReactMeteor.createClass which allows me to subscribe to publications without the getMeteorData() { var handler=Meteor.subscribe(...); if (ready()) { Entry.find(...);}} stuff - this gets a bit unwieldy in my app.

I have not found a better way when using the ReactMeterData mixin and I watched this tallk so I gave reactjs:react a try - it worked with some work arounds :( I've tried to eliminate the Meteor stuff from my trouble area and I can't narrow it down to the Meteor stuff as the component doesn't work either way. I'm open to using the mixin though.

Anyway I am using ReactLayout and FlowRouter


   var publicGroup =  FlowRouter.group({});
publicGroup.route('/vote', {
      name: 'vote',
      action() {
        ReactLayout.render(AppLayout, {
          content: <Pages.VotePage />
        })
      }
    });

That class in the issue is Page.VotePage.

froatsnook commented 8 years ago

I don't really like either ReactMeteorData or ReactMeteor for subscriptions. Instead I start the subscriptions in my iron-router's waitOn (not sure if there's something similar in FlowRouter), and then in the component I do this:

compoentWillMount() {
    this.handle = Deps.autorun(() => {
        var votes = Votes.find({ election: this.props.electionId }).fetch();
        this.setState({ votes });
    });
},

componentWillUnmount() {
    this.handle.stop();
},

I like doing it this way since I can decide when I want to update the state and control what's reactive (this is important e.g. in some cases for drag and drop).

Anyway, sorry I can't help you figure out your onChange problem.

sdbondi commented 8 years ago

Actually you have! Well I still have no idea why it broke, but just removing reactjs:react and just having the react package + removing all the ReactMeteor classes for now and the component above works!

FlowRouter does have a subscribe mechanism so I'll play with that, might put that in a statics:{ subscriptions(){} } and call Page.VotePage.subscriptions() or something as I prefer seeing what the component needs inside the component.

Thanks for your help - closing!

froatsnook commented 8 years ago

Oh that's interesting. It still worked in my test app with both packages.

No problem and good luck with your app!

sdbondi commented 8 years ago

Ye so many deps, packages and reacts going on - really tricky to track down, will work on minimizing them later - thanks for taking the time to help, my first production meteor/react app - been interesting, not as quick as I'd hoped but great fun!

sdbondi commented 8 years ago

BTW I made this mixin (may not like em, but they can make things work in a bind):

export const MeteorReactSubscriber = {
  componentWillMount() {
    this._subsMap = {};
    this.subscriptions();
    this._startMeteorTracker();
  },

  _startMeteorTracker() {
    this._subsHandle = Tracker.autorun(() => {
      this.subscriptionReady && this.subscriptionReady();
      if (this.meteorSubsReady()) {
        this.meteorDataReady && this.meteorDataReady();
      }
    });
  },

  meteorSubsReady() {
    return _.all(this._subsMap, (s, _) => s.ready());
  },

  meteorSubscribe(name, selector=undefined) {
    this._subsMap[name] = Meteor.subscribe(name, selector);
  },
  componentWillUnmount() {
    this._subsHandle.stop();
    this._subsMap = null;
  }
};

Usage

var SweetComponent = React.createClass({
  mixins: [MeteorReactSubscriber],
  subscriptions() {  
    this.meteorSubscribe('entries');
  },
meteorDataReady() { 
  var currentEntry = Entry.findOne(this.props.entryId);
  this.setState({entry});
}
});