1

I have a PageComponent it contains following react components :-

  1. Add item form. [FormComponent]
  2. Paginated List of items. [UserListComponent]

When user clicks on Add Item in Add item form. A action is called using ActionCreator and then it calls the API server along with Success/Failure callback.

//Calling action action creator from the component
ActionCreator.addUser({email:email, dept_code:dept});


//Action Creator calling action using dispatcher.
addUser: function(userObj){
  ApiClient.addUser(
    userObj,
    function(response){
       AppDispatcher.handleServerAction({actionType:ActionTypes.ADD_USER_SUCCESS, response: response});
    }.bind(this),
    function(error){
       AppDispatcher.handleServerAction({actionType:ActionTypes.ADD_USER_FAIL, error: error});
    }.bind(this)
  );
}

When success/failure callback is called, it would dispatch an action for example ADD_USERS_SUCCESS.

I have configured the PageStore to listen to this action and inform the user that form is submitted.

  dispatcherIndex: AppDispatcher.register(function(payload) {
      var action = payload.action;
      switch(action.actionType){
         case ActionTypes.LOAD_USERS_SUCCESS:
            persistStoreData(false, null, action.usersObj);
            break;
         case ActionTypes.LOAD_USERS_FAIL:
            persistStoreData(false, payload.action.error, {});
            break;
         case ActionTypes.ADD_USER_SUCCESS:
            updateAddFormData(false, "Added", "success", []);
            break;
         case ActionTypes.ADD_USER_FAIL:
            updateAddFormData(true, "Add Failed! Click to retry..", "danger", payload.action.error);
            break;
         default:
            return true;
      }
      UsersStore.emitChange();
      return true; // No errors. Needed by promise in Flux Dispatcher.
 })

The problem is that how do I update my UserListComponent if ADD_USERS_SUCCESS action is triggered.

I have following solution in mind :-

  1. Triggering an Action (e.g.LOAD_USERS) which would list users on my render method like , if I have a flag in my state like {reloadUserTable: true} ?

  2. Updating the state in render method but as per Facebook docs updating state in the render method is AntiPattern.

techgyani
  • 535
  • 8
  • 24

4 Answers4

1

You can maintain states inside PageComponent and let it's 'children' (UserListComponent) access it using props property.

var PageComponent = React.createClass({
         getUserState: function() {
            return {
                allUsers: UsersStore.getAllUsers()
            }
         },
         getInitialState: function() {
            return getUserState();
         },
         /* at ADD_USERS_SUCCESS */
         onChange: function() {
            this.setState(getUserState());
         },
         render: function() {
            <FormComponent />
            {/* inside UserListComponent, access all users using this.props.users */}
            <UserListComponent users={this.state.allUsers} />
         }});
Oppo
  • 11
  • 2
  • If I might make a suggestion, some description of what your code does might help the questioner. – Rüdiger Herrmann Feb 23 '15 at 18:00
  • Is calling store from the View is okay in "onChange" event or I should Call action like below : `onChange: function() { ActionCreator.loadUsers({page:1}); ; },` – techgyani Feb 24 '15 at 04:09
  • After the stores update themselves in response to an action, they emit change event. Views listen for change events, retrieve the new data from the stores. In other words its perfectly fine to call Store from the Views. There is no need to call another action onChange. – Oppo Feb 24 '15 at 06:20
  • Thanks, I understand what you are saying. I need to trigger the Action to fetch user list data from the web server because I read somewhere that store shouldn't talk to Web-services directly. – techgyani Feb 24 '15 at 06:27
  • Yes, Web-services on success/fail should call action with data which is passed to Stores through dispatcher and then Stores can update themselves with new data & emit changes. – Oppo Feb 24 '15 at 06:50
  • I agree with you. I have written a complete answer on the basis of this discussion below. http://stackoverflow.com/a/28689797/1922960 – techgyani Feb 24 '15 at 06:57
0

If your PageStore contains a reference to pages which is linked to UserListComponent component then on success you can add to that reference and emit change.

pra
  • 343
  • 1
  • 3
  • 16
  • Thanks, the problem is that this approach would require lot of work related to maintenance of pagination on client side. So I want to fetch the user list from server. – techgyani Feb 24 '15 at 04:06
  • If you are always going to load the users (along with pagination) then why not fire the load event along with ADD_USER_SUCCESS event? – pra Feb 24 '15 at 10:53
0

simply expose a method on the child component for the parent to call - http://facebook.github.io/react/tips/expose-component-functions.html

Alex
  • 1
  • Please elaborate on your answer, do not simply reference documentation. How does this doc relate to the question? – mmmmmpie Feb 23 '15 at 16:58
0

My working solution to solve this problem is like below. The _onChange callback in the solution given by @oppo helped me.

What I did to solve this problem :- 1. When add user action is called, I am setting a flag on my store like {reloadUsers:true} 2. View Component's _onChange callback check for this flag if it is true then it triggers a action to load the data from API server.

Following is the Store

'use strict';
var AppDispatcher = require('../dispatcher/AppDispatcher');
var Constants = require('../constants/Constants');
var EventEmitter = require('events').EventEmitter;
var assign = require('object-assign');

var ActionTypes = Constants.ActionTypes;
var CHANGE_EVENT = 'change';
var _state = {
               loading: false,
               error : null,
               users: {},
               isAdded: true,
               addFormErrors: [],
               reloadUsers: false //Whether to reload user table or not
            };
//Stores data recieved from server on page load
function persistStoreData(loading, error, response) {
   _state.loading = loading;
   _state.error = error;
   _state.users = response;
   _state.reloadUsers = false;
}

//Updates data recieved from server if data saved
function updateAddFormData(enableSave){
   _state.enableAddButton = enableSave;
   if(!_state.enableAddButton){
      _state.isAdded = true;
      _state.reloadUsers = true;
   }
   else{
      _state.isAdded = false;
   }
}

var UsersStore = assign({}, EventEmitter.prototype, {
   getState: function(){
      return _state;
   },

   getUsers: function(){
      return this._users;
   },

   emitChange: function() {
    //console.log('store change event');
    this.emit(CHANGE_EVENT);
   },

   /**
   * @param {function} callback
   */
  addChangeListener: function(callback) {
    this.on(CHANGE_EVENT, callback);
  },

  /**
   * @param {function} callback
   */
  removeChangeListener: function(callback) {
    this.removeListener(CHANGE_EVENT, callback);
  },

  dispatcherIndex: AppDispatcher.register(function(payload) {
      var action = payload.action;
      switch(action.actionType){
         case ActionTypes.LOAD_USERS_SUCCESS:
            persistStoreData(false, null, action.usersObj);
            break;
         case ActionTypes.LOAD_USERS_FAIL:
            persistStoreData(false, payload.action.error, {});
            break;
         case ActionTypes.ADD_USER_SUCCESS:
            updateAddFormData(false, "Added", "success", []);
            break;
         case ActionTypes.ADD_USER_FAIL:
            updateAddFormData(true, payload.action.error);
            break;
         default:
            return true;
      }
      UsersStore.emitChange();
      return true; // No errors. Needed by promise in Flux Dispatcher.
 })

});
module.exports = UsersStore;

Following is the dispatcher

'use strict';
var Constants = require('../constants/Constants');
var Dispatcher = require('flux').Dispatcher;
var assign = require('object-assign');

var PayloadSources = Constants.PayloadSources;

var AppDispatcher = assign(new Dispatcher(), {

  /**
   * @param {object} action The details of the action, including the action's
   * type and additional data coming from the server.
   */
  handleServerAction: function(action) {
    var payload = {
      source: PayloadSources.SERVER_ACTION,
      action: action
    };
    this.dispatch(payload);
  }

});

module.exports = AppDispatcher;

Following are my constants

var keyMirror = require('keymirror');

module.exports = {
   ActionTypes: keyMirror({
      ADD_USER_SUCCESS: null,
      ADD_USER_FAIL: null,

      LOAD_USERS: null,
      LOAD_USERS_SUCCESS: null,
      LOAD_USERS_FAIL: null,
   }),
   PayloadSources: keyMirror({
      SERVER_ACTION: null,
   })
};

Following is the Action creator

'use strict';
var AppDispatcher = require('../dispatcher/AppDispatcher');
var Constants = require('../constants/Constants');
var ApiClient = require('../clients/ApiClient');
var ActionTypes = Constants.ActionTypes;

var ActionCreator = {

  loadUsers: function(){
     ApiClient.getUsers(function(usersObj) {
       AppDispatcher.handleServerAction({actionType:ActionTypes.LOAD_USERS_SUCCESS, usersObj: usersObj});
     }.bind(this), function(error) {
      AppDispatcher.handleServerAction({actionType:ActionTypes.LOAD_USERS_FAIL, error: error});
     }.bind(this));
   }

    addUser: function(userObj){
      ApiClient.addUser(
        userObj,
        function(response){
           AppDispatcher.handleServerAction({actionType:ActionTypes.ADD_USER_SUCCESS, response: response});
        }.bind(this),
        function(error){
           AppDispatcher.handleServerAction({actionType:ActionTypes.ADD_USER_FAIL, error: error});
        }.bind(this)
      );
    }
};
module.exports = ActionCreator;

Following is the main the view component ( only important parts )

'use strict';
var React = require('react');
var ActionCreator = require('../actions/ActionCreator');
var UsersStore = require('../stores/UsersStore');
var UsersTable = require('./UsersTable.jsx');
var UserAddForm = require('./UserAddForm.jsx');
var ManageUsers = React.createClass({

  getInitialState: function() {
      return UsersStore.getState();
  },

  componentDidMount: function() {
      ActionCreator.loadUsers();//Invoking Action, loading initial data
      UsersStore.addChangeListener(this._onChange);
  },

  componentWillUnmount: function() {
     UsersStore.removeChangeListener(this._onChange);
  },

  _onChange: function() {
     var state = UsersStore.getState();
     if(state.reloadUsers === true){
       //Only reload users if state has this variable set
       ActionCreator.loadUsers();
     }
     else{
       this.setState(state);
     }
  },
  render: function(){
    //Rendering logic would go here, below is just a prototype
    return (
       <div>
       <UserAddForm onSubmit={handleFormSubmit} />
       <UsersTable/>
       </div>
    );
  }
});

module.exports = ManageUsers;

Following is API Client

'use strict';
var $ = require('jquery');
var ApiClient = {

   getUsers : function(success, failure){
      $.ajax({
         url : '/api/get-users',
         dataType: 'json',
         success : function(data){
            success(data);
         },
         error : function(jqXHR, textStatus, errorThrown){
            failure(errorThrown);
         }
      });
   },

   addUser : function(data, success, failure){
      $.ajax({
         url : '/api/add-user',
         dataType: 'json',
         type: 'post',
         data: 'data='+JSON.stringify(data),
         success : function(data){
            success(data);
         },
         error : function(jqXHR){
            var errorObj = {};
            try{
              errorObj = JSON.parse(jqXHR.responseText);
            }
            catch(e){
              errorObj['main'] = "An error occured";
            }
            failure(errorObj);
         }
      });
   }
};
module.exports = ApiClient;
techgyani
  • 535
  • 8
  • 24