Short answer
- The form component must retrieve its state from the Repository, create an “update” at the user’s inputs and invoke the “save” action in the submit form.
- The creators of the action will execute the POST request and initiate the save_success or save_error action depending on the results of the request.
Long answer through an example implementation
apiUtils / BarAPI.js
var Request = require('./Request'); //it a custom module that handles request via superagent wrapped in Promise var BarActionCreators = require('../actions/BarActionCreators'); var _endpoint = 'http://localhost:8888/api/bars/'; module.exports = { post: function(barData) { BarActionCreators.savePending(); Request.post(_endpoint, barData).then (function(res) { if (res.badRequest) { //ie response returns code 400 due to validation errors for example BarActionCreators.saveInvalidated(res.body); } BarActionCreators.savedSuccess(res.body); }).catch( function(err) { //server errors BarActionCreators.savedError(err); }); }, //other helpers out of topic for this answer };
Actions / BarActionCreators.js
var AppDispatcher = require('../dispatcher/AppDispatcher'); var ActionTypes = require('../constants/BarConstants').ActionTypes; var BarAPI = require('../apiUtils/VoucherAPI'); module.exports = { save: function(bar) { BarAPI.save(bar.toJSON()); }, saveSucceed: function(response) { AppDispatcher.dispatch({ type: ActionTypes.BAR_SAVE_SUCCEED, response: response }); }, saveInvalidated: function(barData) { AppDispatcher.dispatch({ type: ActionTypes.BAR_SAVE_INVALIDATED, response: response }) }, saveFailed: function(err) { AppDispatcher.dispatch({ type: ActionTypes.BAR_SAVE_FAILED, err: err }); }, savePending: function(bar) { AppDispatcher.dispatch({ type: ActionTypes.BAR_SAVE_PENDING, bar: bar }); } rehydrate: function(barId, field, value) { AppDispatcher.dispatch({ type: ActionTypes.BAR_REHYDRATED, barId: barId, field: field, value: value }); }, };
shops / BarStore.js
var assign = require('object-assign'); var EventEmitter = require('events').EventEmitter; var Immutable = require('immutable'); var AppDispatcher = require('../dispatcher/AppDispatcher'); var ActionTypes = require('../constants/BarConstants').ActionTypes; var BarAPI = require('../apiUtils/BarAPI') var CHANGE_EVENT = 'change'; var _bars = Immutable.OrderedMap(); class Bar extends Immutable.Record({ 'id': undefined, 'name': undefined, 'description': undefined, 'save_status': "not saved" //better to use constants here }) { isReady() { return this.id != undefined //usefull to know if we can display a spinner when the Bar is loading or the Bar data if it is ready. } getBar() { return BarStore.get(this.bar_id); } } function _rehydrate(barId, field, value) { //Since _bars is an Immutable, we need to return the new Immutable map. Immutable.js is smart, if we update with the save values, the same reference is returned. _bars = _bars.updateIn([barId, field], function() { return value; }); } var BarStore = assign({}, EventEmitter.prototype, { get: function(id) { if (!_bars.has(id)) { BarAPI.get(id); //not defined is this example return new Bar(); //we return an empty Bar record for consistency } return _bars.get(id) }, getAll: function() { return _bars.toList() //we want to get rid of keys and just keep the values }, Bar: Bar, emitChange: function() { this.emit(CHANGE_EVENT); }, addChangeListener: function(callback) { this.on(CHANGE_EVENT, callback); }, removeChangeListener: function(callback) { this.removeListener(CHANGE_EVENT, callback); }, }); var _setBar = function(barData) { _bars = _bars.set(barData.id, new Bar(barData)); }; BarStore.dispatchToken = AppDispatcher.register(function(action) { switch (action.type) { case ActionTypes.BAR_REHYDRATED: _rehydrate( action.barId, action.field, action.value ); BarStore.emitChange(); break; case ActionTypes.BAR_SAVE_PENDING: _bars = _bars.updateIn([action.bar.id, "save_status"], function() { return "saving"; }); BarStore.emitChange(); break; case ActionTypes.BAR_SAVE_SUCCEED: _bars = _bars.updateIn([action.bar.id, "save_status"], function() { return "saved"; }); BarStore.emitChange(); break; case ActionTypes.BAR_SAVE_INVALIDATED: _bars = _bars.updateIn([action.bar.id, "save_status"], function() { return "invalid"; }); BarStore.emitChange(); break; case ActionTypes.BAR_SAVE_FAILED: _bars = _bars.updateIn([action.bar.id, "save_status"], function() { return "failed"; }); BarStore.emitChange(); break; //many other actions outside the scope of this answer default: break; } }); module.exports = BarStore;
components / BarList.react.js
var React = require('react/addons'); var Immutable = require('immutable'); var BarListItem = require('./BarListItem.react'); var BarStore = require('../stores/BarStore'); function getStateFromStore() { return { barList: BarStore.getAll(), }; } module.exports = React.createClass({ getInitialState: function() { return getStateFromStore(); }, componentDidMount: function() { BarStore.addChangeListener(this._onChange); }, componentWillUnmount: function() { BarStore.removeChangeListener(this._onChange); }, render: function() { var barItems = this.state.barList.toJS().map(function (bar) {
components / BarListItem.react.js
var React = require('react/addons'); var ImmutableRenderMixin = require('react-immutable-render-mixin') var Immutable = require('immutable'); module.exports = React.createClass({ mixins: [ImmutableRenderMixin], // I use propTypes to explicitly telling // what data this component need. This // component is a standalone component // and we could have passed an entire // object such as {id: ..., name, ..., description, ...} // since we use all the datas (and when we use all the data it's // a better approach since we don't want to write dozens of propTypes) // but let do that for the example sake propTypes: { id: React.PropTypes.number.isRequired, name: React.PropTypes.string.isRequired, description: React.PropTypes.string.isRequired } render: function() { return ( <li> //we should wrapped the following p in a Link to the editing page of the Bar record with id = this.props.id. Let assume that what we did and when we click on this <li> we are redirected to edit page which renders a BarDetail component <p>{this.props.id}</p> <p>{this.props.name}</p> <p>{this.props.description}</p> </li> ) } });
components / BarDetail.react.js
var React = require('react/addons'); var ImmutableRenderMixin = require('react-immutable-render-mixin') var Immutable = require('immutable'); var BarActionCreators = require('../actions/BarActionCreators'); module.exports = React.createClass({ mixins: [ImmutableRenderMixin], propTypes: { id: React.PropTypes.number.isRequired, name: React.PropTypes.string.isRequired, description: React.PropTypes.string.isRequired }, handleSubmit: function(event) {
In this basic example, whenever a user edits a Bar element through a form in the BarDetail component, the basic Bar record will be kept up to date locally, and when the form is submitted, we will try to save it to the server. What is it:)