Where To Have API Calls In React Flux

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.

Here’s an example of the module to make the asynchronous call. I used superagent to make the request.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var Api = {
get: function (url) {
return new Promise(function (resolve, reject) {
request
.get(url)
.end(function (res) {
if (res.status === 404) {
reject();
} else {
resolve(JSON.parse(res.text));
}
});
});
}
};

The action creator would use this module. When the promise is returned dispatch an action containing the result.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var Api = require('./Api');
var Dispatcher = require('./Dispatcher');
var ActionConstants = require('./ActionConstants');
// Define the ActionCreator.
var ActionCreator = {
getCategories: function () {
Api
.get('/api/categories')
.then(function (categories) {
// Dispatch an action containing the categories.
Dispatcher.handleViewAction({
actionType: ActionConstants.RECEIVE_CATEGORIES,
categories: categories
});
});
};
};

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
var Dispatcher = require('./Dispatcher');
var ActionConstants = require('./ActionConstants');
var EventEmitter = require('events').EventEmitter;
var assign = require('object-assign');
var CHANGE_EVENT = 'change';
var _categories = [];
function setCategories (categories) {
_categories = categories;
}
// Define the Store.
var Store = assign({}, EventEmitter.prototype, {
emitChange: function () {
this.emit(CHANGE_EVENT);
},
addChangeListener: function (callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener: function (callback) {
this.removeListener(CHANGE_EVENT, callback);
},
getCategories: function () {
return _categories;
}
});
// Store registers with dispatcher to handle actions.
Store.dispatchToken = Dispatcher.register(function (payload) {
var action = payload.action;
switch (action.actionType) {
case ActionConstants.RECEIVE_CATEGORIES:
// Callback to handle the response from the action.
setCategories();
break;
default:
return true;
break;
}
Store.emitChange();
return true;
});

Finally our components would use the store to register change listeners and the action creator for getting our categories!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
var Store = require('./Store');
var ActionCreator = require('./ActionCreator');
// Define the Category component.
var Category = React.createClass({
getInitialState: function () {
return {
categories: []
};
},
componentWillMount: function () {
Store.addChangeListener(this._onChange);
},
// Use the ActionCreator to get the categories.
componentDidMount: function () {
ActionCreator.getCategories();
},
componentWillUnmount: function () {
Store.removeChangeListener(this._onChange);
},
/**
* Update the state of categories for this component.
* This will get called when our store handles the response
* from the action.
*/
_onChange: function () {
this.setState({
categories: Store.getCategories()
});
},
// Display a drop-down containg the categories.
render: function () {
var categories;
if (this.state.categories) {
categories = this.state.categories.map(function (category) {
return <option key={ category.id }
value={ category.name }>
{ category.name }</option>;
});
}
return (
<div>
<select name="category">
<option value="">Select a Category</option>
{ categories }
</select>
</div>
);
}
});

final thoughts

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).

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.

Check out the repo on github: react-flux-api-calls

Questions? Comments? Tweet me @schempy or email schempysays@gmail.com

React Flux and Routing For Seo

Single page web applications are awesome. Recently I used React/Flux to build a simple application to test if I can get SEO working without being a real pain. The application would display products based on a category. The user would select a category from a drop-down. Once a category has been selected a list of products in that category would display.

I wanted to have a client-side router. I chose react-router. Selecting a category would push a url to the browser history. The user would be able to refresh the browser with the new url and have the contents of the page be SEO friendly.

the challenge

On the server-side I needed the following:

  1. Initialize the React application.
  2. Set the state for the React components.
  3. Render the React components to html.
  4. Return the page that will be SEO friendly. Passing the initial state of the components.

On the client-side I needed the following:

  1. Initialize the React application.
  2. Set the state for the React components using the initial state passed from the server.
  3. Render the React components. If the components contain the same initial state from the server, React is smart enough to know not to re-render the components.

The initial state passed from the server would be JSON stored in a javascript variable.

1
2
3
<script type="text/javascript">
window.App={"products:[{"id": 1, "name":"Led Zeppelin"},{"id": 2, "name":"Bob Marley"}]};
</script>

Read More