stkvtflw
stkvtflw

Reputation: 13507

How do I handle Store's listeneres in ReactJS?

Here is the average Store:

// StoreUser
import EventEmitter from 'eventemitter3';
import Dispatcher from '../dispatcher/dispatcher';
import Constants from '../constants/constants';
import React from 'react/addons';
import serverAddress from '../utils/serverAddress';
import socket from './socket';

var CHANGE_EVENT = "change";
var storedData = {
  userName: null
}

socket
  .on('newUserName', (newUserName)=>{
    storedData.userName = newUserName;
    AppStore.emitChange();
  })


function _changeUserName (newUserName) {
  socket.emit('signUp', newUserName)
}

var AppStore = React.addons.update(EventEmitter.prototype, {$merge: {

  emitChange(){
    // console.log('emitChange')
    this.emit(CHANGE_EVENT);
  },

  addChangeListener(callback){
    this.on(CHANGE_EVENT, callback)
  },

  removeChangeListener(callback){
    this.removeListener(CHANGE_EVENT, callback)
  },

  getStoredData(callback) { 
    callback(storedData);
  },

  dispatcherIndex:Dispatcher.register(function(payload){
    var action = payload.action;
    switch(action.actionType){
      case Constants.CHANGE_USER_NAME:
        _changeUserName(payload.action.actionArgument);
        break;
    }
    return true;
  })
}});

export default AppStore;

Here is average component, connected to the store:

import React from 'react';

import StoreUser from '../../stores/StoreUser';

import Actions from '../../actions/Actions';

export default class User extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      userName: null,
    };
  }

  componentDidMount() {
    StoreUser.addChangeListener(this._onChange.bind(this));
  }

  componentWillUnmount() {
    StoreUser.removeChangeListener();
  }

  render() {
    const { userName } = this.state;
    return (
      <div key={userName}>
        {userName}
      </div>
    );
  }
  _onChange(){
    StoreUser.getStoredData(_callbackFunction.bind(this))
    function _callbackFunction (storedData) {
      // console.log('storedData', storedData)
      this.setState({
        userName: storedData.userName,
      })
    }
  }

}

This was working absolutely fine. But now, I'm facing quite big mistake in this approach:
WebApp has, say, sidebar with user's name, which is listening to StoreUser in order to display up to date userName. And also, the webapp has a component (page), which contains user's profile (including userName), which is also listening to StoreUser. If I will unmount this component, then the sidebar will stop listening to StoreUser too.

I've learned this approach from egghead.io or some other tutorial - I don't remember, where exactly. Seems, I've missed some important point about how to manage store listeners. Could you, please, suggest simple approach to solve the described issue?

I'm trying to stick to the original Flux architecture, please, don't advice me any alternative implementations of Flux architecture. This is the only issue I have.

Upvotes: 0

Views: 127

Answers (1)

TomW
TomW

Reputation: 4002

The issue is that you aren't removing the your listener when you unmount. You have to be very careful to remove exactly the same function reference that you added (passing this.callback.bind(this) twice doesn't work, since the two calls return different function references). The simplest approach would be:

//...
export default class User extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      userName: null,
    };
    // create fixed bound function reference we can add and remove
    // (like the auto-binding React.createComponent does for you).
    this._onChange = this._onChange.bind(this);
  }

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

  componentWillUnmount() {
    // make sure you remove your listener!
    StoreUser.removeChangeListener(this._onChange);
  }
//...

There are a couple of oddities with your code:

  • Why does StoreUser.getStoredData take a callback, rather than just returning the data? With flux, you should always query synchronously. If data needs fetching, the getter could fire off the ajax fetch, return null, and then trigger another action when the ajax request returns, causing another emitChange and hence ultimately triggering the re-render with the user data.
  • It looks like your store is triggering server writes. Normally, in flux, this would be the responsibility of an action creator rather than the store, though I don't think it's a major violation for the store to do it.

Upvotes: 1

Related Questions