56 | );
57 |
58 | }
59 | /* jshint ignore:end */
60 |
61 | });
62 |
63 | module.exports = Category;
64 |
--------------------------------------------------------------------------------
/lib/stores/Store.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Dispatcher = require('../core/Dispatcher');
4 | var ActionConstants = require('../constants/ActionConstants');
5 | var EventEmitter = require('events').EventEmitter;
6 | var assign = require('object-assign');
7 |
8 | var CHANGE_EVENT = 'change',
9 | _categories = [];
10 |
11 | /**
12 | * Set the values for categories that will be used
13 | * with components.
14 | */
15 | function setCategories (categories) {
16 | _categories = categories;
17 | }
18 |
19 | var Store = assign({}, EventEmitter.prototype, {
20 |
21 | /**
22 | * Emits change event.
23 | */
24 | emitChange: function () {
25 | this.emit(CHANGE_EVENT);
26 | },
27 |
28 | /**
29 | * Adds a change listener.
30 | *
31 | * @param {function} callback Callback function.
32 | */
33 | addChangeListener: function (callback) {
34 | this.on(CHANGE_EVENT, callback);
35 | },
36 |
37 | /**
38 | * Removes a change listener.
39 | *
40 | * @param {function} callback Callback function.
41 | */
42 | removeChangeListener: function (callback) {
43 | this.removeListener(CHANGE_EVENT, callback);
44 | },
45 |
46 | /**
47 | * Return the value for categories.
48 | */
49 | getCategories: function () {
50 | return _categories;
51 | }
52 | });
53 |
54 | Store.dispatchToken = Dispatcher.register(function (payload) {
55 | var action = payload.action;
56 |
57 | switch (action.actionType) {
58 | case ActionConstants.RECEIVE_CATEGORIES:
59 | setCategories(action.categories);
60 | break;
61 |
62 | default:
63 | return true;
64 | }
65 |
66 | Store.emitChange();
67 |
68 | return true;
69 | });
70 |
71 | module.exports = Store;
72 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Working with React/Flux the last three months I couldn't decide where to make asynchronous calls. Should they be made in the component, store or action creators? I chose the action creators since dispatching of all actions come from them. A module could abstract the actual asynchronous call and return a promise. The promise would resolve with the result of the call or be rejected if there was an error.
2 |
3 | Here's an example of the module to make the asynchronous call. I used [superagent](https://github.com/visionmedia/superagent) to make the request.
4 |
5 | ```js
6 | var Api = {
7 | get: function (url) {
8 | return new Promise(function (resolve, reject) {
9 | request
10 | .get(url)
11 | .end(function (res) {
12 | if (res.status === 404) {
13 | reject();
14 | } else {
15 | resolve(JSON.parse(res.text));
16 | }
17 | });
18 | });
19 | }
20 | };
21 | ```
22 |
23 | The action creator would use this module. When the promise is returned dispatch an action containing the result.
24 |
25 | ```js
26 | var Api = require('./Api');
27 | var Dispatcher = require('./Dispatcher');
28 | var ActionConstants = require('./ActionConstants');
29 |
30 | // Define the ActionCreator.
31 | var ActionCreator = {
32 | getCategories: function () {
33 | Api
34 | .get('/api/categories')
35 | .then(function (categories) {
36 |
37 | // Dispatch an action containing the categories.
38 | Dispatcher.handleViewAction({
39 | actionType: ActionConstants.RECEIVE_CATEGORIES,
40 | categories: categories
41 | });
42 | });
43 | };
44 | };
45 | ```
46 |
47 | The store would register with the dispatcher and provide a callback to handle the response from the action. It would also emit a change event so components would be notified that values have changed.
48 |
49 | ```js
50 | var Dispatcher = require('./Dispatcher');
51 | var ActionConstants = require('./ActionConstants');
52 | var EventEmitter = require('events').EventEmitter;
53 | var assign = require('object-assign');
54 |
55 | var CHANGE_EVENT = 'change';
56 | var _categories = [];
57 |
58 | function setCategories (categories) {
59 | _categories = categories;
60 | }
61 |
62 | // Define the Store.
63 | var Store = assign({}, EventEmitter.prototype, {
64 |
65 | emitChange: function () {
66 | this.emit(CHANGE_EVENT);
67 | },
68 |
69 | addChangeListener: function (callback) {
70 | this.on(CHANGE_EVENT, callback);
71 | },
72 |
73 | removeChangeListener: function (callback) {
74 | this.removeListener(CHANGE_EVENT, callback);
75 | },
76 |
77 | getCategories: function () {
78 | return _categories;
79 | }
80 | });
81 |
82 | // Store registers with dispatcher to handle actions.
83 | Store.dispatchToken = Dispatcher.register(function (payload) {
84 | var action = payload.action;
85 |
86 | switch (action.actionType) {
87 | case ActionConstants.RECEIVE_CATEGORIES:
88 |
89 | // Callback to handle the response from the action.
90 | setCategories();
91 | break;
92 |
93 | default:
94 | return true;
95 | break;
96 | }
97 |
98 | Store.emitChange();
99 |
100 | return true;
101 | });
102 | ```
103 |
104 | Finally our components would use the store to register change listeners and the action creator for getting our categories!
105 |
106 | ```js
107 | var Store = require('./Store');
108 | var ActionCreator = require('./ActionCreator');
109 |
110 | // Define the Category component.
111 | var Category = React.createClass({
112 |
113 | getInitialState: function () {
114 | return {
115 | categories: []
116 | };
117 | },
118 |
119 | componentWillMount: function () {
120 | Store.addChangeListener(this._onChange);
121 | },
122 |
123 | // Use the ActionCreator to get the categories.
124 | componentDidMount: function () {
125 | ActionCreator.getCategories();
126 | },
127 |
128 | componentWillUnmount: function () {
129 | Store.removeChangeListener(this._onChange);
130 | },
131 |
132 | /**
133 | * Update the state of categories for this component.
134 | * This will get called when our store handles the response
135 | * from the action.
136 | */
137 | _onChange: function () {
138 | this.setState({
139 | categories: Store.getCategories()
140 | });
141 | },
142 |
143 | // Display a drop-down containg the categories.
144 | render: function () {
145 | var categories;
146 |
147 | if (this.state.categories) {
148 | categories = this.state.categories.map(function (category) {
149 | return ;
152 | });
153 | }
154 |
155 | return (
156 |
157 |
161 |
162 | );
163 | }
164 | });
165 | ```
166 |
167 |
168 | ## final thoughts
169 | The result from an asynchronous call should create an action. This keeps the data flowing through the application the flux way (Actions -> Dispatcher -> Stores -> Views).
170 |
171 | One issue having the category component make a Api call once it's mounted is it will be rendered twice. Having components get their data from props is better but sometimes the data has to be from an external service.
172 |
173 | ## running the app
174 | Install all the node modules
175 |
176 | ``` js
177 | $ npm install
178 | ```
179 |
180 | Start the development server. This will use browserify to build the javascript
181 | and watchify to re-build the javascript on any changes.
182 |
183 | ``` js
184 | $ npm run start-dev
185 | ```
186 |
187 | To start the server without any javascript watching
188 |
189 | ``` js
190 | $ npm start
191 | ```
192 |
193 | Point your browser to http://localhost:5000
194 |
195 | Checkout the package.json file for the npm scripts.
--------------------------------------------------------------------------------