Exercise 5: Solution

In notebook:
FrontEndMasters React
Created at:
2016-06-17
Updated:
2016-06-17
Tags:
libraries React JavaScript
Start with view, add a button
  // **** components/app.js

var React = require('react');
var ContactsStore = require('../stores/ContactsStore');
var ViewActionCreators = require('../actions/ViewActionCreators');

var App = React.createClass({
  getInitialState () {..},

  componentDidMount () {..},

  componentWillUnmount () {.. },

  handleStoreChange () {.. },

    // 2. ++++ add the click handler
  deleteContact (contact) {
    ViewActionCreators.deleteContact(contact);
  },

  renderContacts () {
    return this.state.contacts.map((contact) => {
      return <li>
        {contact.first} {contact.last}
        // 1. ++++ add the button and click handler
        <button onClick={this.deleteContact.bind(this, contact)}>
          delete
        </button>
      </li>;
    });
  },

  render () {
    if (!this.state.loaded) {..}

    return (
      <div>
        <ul>{this.renderContacts()}</ul>
      </div>
    );
  }
});

module.exports = App;
Then continues with ViewActionCreators 
  var { ActionTypes } = require('../Constants');
var AppDispatcher = require('../AppDispatcher');
var ApiUtil = require('../utils/ApiUtil');

var ViewActionCreators = {
  loadContacts () {
    AppDispatcher.handleViewAction({
      type: ActionTypes.LOAD_CONTACTS
    });
    ApiUtil.loadContacts();
  },
    // 1. ++++ add deleteContact
  deleteContact (contact) {
    AppDispatcher.handleViewAction({
      type: ActionTypes.CONTACT_DELETED,
      contact: contact
    });
    ApiUtil.deleteContact(contact);
  }
};

module.exports = ViewActionCreators;
​deleteContact​ can be called from multiple places in your app. 
Then moves to utils/ApiUtil.js
  // **** utils/ApiUtil.js

var xhr = require('../lib/xhr');
var { API, ActionTypes } = require('../Constants');
var ServerActionCreators = require('../actions/ServerActionCreators');

var ApiUtils = {
  loadContacts () {
    xhr.getJSON(`${API}/contacts`, (err, res) => {
      ServerActionCreators.loadedContacts(res.contacts);
    });
  },

    // 1. ++++ add deleteContact
  deleteContact (contact) {
    //   send it to thet contact's `id`
    xhr.deleteJSON(`${API}/contacts/${contact.id}`, (err, res) => {
        // the only way you know this action has finished
        // is because there's another action you care about
        // that is fired
      ServerActionCreators.deletedContact(contact);
    });
  }
};

module.exports = ApiUtils;

Then moves the ServerActionCreator
  var { ActionTypes } = require('../Constants');
var AppDispatcher = require('../AppDispatcher');

var ServerActionCreators = {
  loadedContacts (contacts) {
    AppDispatcher.handleServerAction({
      type: ActionTypes.CONTACTS_LOADED,
      contacts: contacts
    });
  },

  // 1. ++++ dispatch the event
  deletedContact (contact) {
    AppDispatcher.handleServerAction({
      type: ActionTypes.CONTACT_DELETED,
      contact: contact
    });
  }

};

module.exports = ServerActionCreators;
Moves to ContactStore.js
  var AppDispatcher = require('../AppDispatcher');
var { EventEmitter } = require('events');
var { ActionTypes } = require('../Constants');
var assign = require('react/lib/Object.assign');

var events = new EventEmitter();
var CHANGE_EVENT = 'CHANGE';

var state = {
  contacts: [],
  loaded: false
};

var setState = (newState) => {
  assign(state, newState);
  // emits the change event ('CHANGE')
  events.emit(CHANGE_EVENT);
};

var ContactsStore = {
  addChangeListener (fn) {
    events.addListener(CHANGE_EVENT, fn);
  },

  removeChangeListener (fn) {
    events.removeListener(CHANGE_EVENT, fn);
  },

  getState () {
    return state;
  }
};

// 0. need to update the dispatch listener here
// the dispatcher will call all of the callbacks that are listed here
ContactsStore.dispatchToken = AppDispatcher.register((payload) => {
  // we save all the info passed in { action }
  var { action } = payload;

  if (action.type === ActionTypes.CONTACTS_LOADED) {
    setState({
      loaded: true,
      contacts: action.contacts
    });
  }

// 1. check for CONTACT_DELETED event
  if (action.type === ActionTypes.CONTACT_DELETED) {
    // change the store's internal state
    var newContacts = state.contacts.filter((contact) => {
      return contact.id !== action.contact.id;
    });
    // setState will emit a change event (see above)
    setState({ contacts: newContacts });
  }

});

module.exports = ContactsStore;
in the end, the store will fire a ‘CHANGE’ event, that the view is listening to:
  // **** components/App.js

..

var App = React.createClass({
 .. 
  // we're listening to changes on ContactsStore
  componentDidMount () {
    ContactsStore.addChangeListener(this.handleStoreChange);
    ViewActionCreators.loadContacts();
  },
 
  handleStoreChange () {
    this.setState(ContactsStore.getState());
  },

  deleteContact (contact) {
    ViewActionCreators.deleteContact(contact);
  },

  renderContacts () {
    ..
  },

  render () {
    ..
  }
});

module.exports = App;