Flux action not updating state, am I doing this wrong?

368 Views Asked by At

I have a Flux problem that's been killing me. I'm calling an action on page load, but for some reason it doesn't update the state in the component. In this example, I have this.props.count set to 5 (the default in TestStore). I then call an action to increase it in componentDidmount to 6, but it doesn't update the component's state. It stays at 5. Then if I click the link to manually update it, it goes from 5 to 7.

I think it has something to do with the Flux changeListener being added to the top-level component after the action is dispatched?

If I put the changeListener in componentWillMount instead of componentDidMount in the top-level component, then everything works. But that doesn't seem like the proper way? I feel like I'm missing something.

Here's a console.log and the components...

console.log

< Tester />

import React from 'react';
import TestActions from '../actions/TestActions';

export default class Tester extends React.Component {

  componentDidMount() {
    // this.props.count defaults to 5
    // This brings it to 6
    TestActions.increaseCount();
  }

  render() {
    return (
      <div>
        // Count should display 6, but shows 5
        Count: {this.props.count} 
        <br />
        <a href="#" onClick={this._handleClick}>Increase</a>
      </div>
    );
  } 

  _handleClick(e) {
    e.preventDefault();
    TestActions.increaseCount();
  }

}

< Application />

import React from 'react';
import {RouteHandler} from 'react-router';
import TestStore from '../stores/TestStore';

export default class Application extends React.Component {

  constructor() {
    super();
    this._onChange = this._onChange.bind(this);
    this.state = this.getStateFromStores();
  }

  getStateFromStores() {
    return {
      count: TestStore.getCount()
    };
  }

  componentDidMount() {
    TestStore.addChangeListener(this._onChange);
  }

  _onChange() {
    this.setState(this.getStateFromStores());
  }

  componentWillUnmount() {
    TestStore.removeChangeListener(this._onChange);
  }

  render() {
    return (
      <RouteHandler {...this.state} {...this.props}/>
    );
  }

}

TestStore

var AppDispatcher = require('../dispatchers/AppDispatcher');
var EventEmitter = require('events').EventEmitter;
var TestConstants = require('../constants/TestConstants');
var assign = require('object-assign');

var CHANGE_EVENT = 'change';

var _count = 5;

function increaseCount() {
  _count = _count + 1;
}

var TestStore = assign({}, EventEmitter.prototype, {

  getCount: function() {
    return _count;
  },

  emitChange: function() {
    console.log('TestStore.emitChange');
    this.emit(CHANGE_EVENT);
  },

  addChangeListener: function(callback) {
    console.log('TestStore.addChangeListener');
    this.on(CHANGE_EVENT, callback);
  },

  removeChangeListener: function(callback) {
    this.removeListener(CHANGE_EVENT, callback);
  }
});

AppDispatcher.register(function(action) {
  var text;

  switch(action.actionType) {
    case TestConstants.INCREASE_COUNT:
      increaseCount();
      TestStore.emitChange();
      break;

    default:
      // no op
  }
});

module.exports = TestStore;
1

There are 1 best solutions below

5
On

As you said, the issue is in <Application />: You start listening to the store in componentDidMount, whereas you should do that in componentWillMount, otherwise you start listening to changes after all the components are mounted, therefore you lose the initial increment.

componentWillMount() {
    TestStore.addChangeListener(this._onChange);
  }

Anyway, I would suggest to perform the action in the top component:

In <Application />

componentDidMount() {
    TestActions.increaseCount();
}, 

_handleClick() {
    TestActions.increaseCount();
},

render() {
   return <Tester callback={this._handleClick} count={this.state.count} />
}

In <Tester/>

<a href="#" onClick={this.props.callback}>Increase</a>