I am currently designing the architecture of a webapp using React.JS. In React, data flow is unidirectional. Sometimes (often) we want to communicate between views. React solves this problem with flux. So far so good, I now have stores that holds the (shared) data.
Now I wonder if flux is also the right solution for this old problem: A view, say a table needs data from a server that it should render. I am not that experienced with React therefor pardon me if it's a stupid question, but is flux also the right solution for getting data from a server and parse it to the registered views? If so, is there a best practice to work with async data? If not, what would be an appropriate way to fetch data from the server for a react view?
I actually don't want a view to call for the data. In my opinion a view should be as stupid as possible...
..is flux also the right solution for getting data from a server and parse it to the registered views?
You can do this with flux. Flux is al about Action Creators. In the flux architecture the idea is that you have a Controller View that registers itself with a Store. The Controller View is your smart component, it gets it state from the Store(s) and usually it renders child components, dumb components, where it passes (some of) the state as properties to it's child(ren).
When for instance you're fetching data from a server, you need to trigger an Action Creator, this Action Creator will then call a Web API utility that performs the actual request. When successful, the Web API utility will call a Server Action Creator that dispatches an action containing the payload received from the server. Any registered Store will then process this action and emit a change event. This way any Controller View interested in a Store, will be notified by it when the Store's data has changed. The Controller View will update it's state and re render itself and any child(ren) to display correct data.
This means that you can call an Action Creator to for instance fetch data from the server in the componentDidMount
(note that this hook is only executed once though!) method of the Controller View.
Initially the Controller View will ask the store for data and get, for example, an empty array, which will be set as the Controller View's state and the Controller View will render something empty. Then after the data has been fetched, the Controller View (who is notified by the Store) will again retrieve the data from the Store, only now it will not be empty. The Controller View updates it's state accordingly, triggering a re render and displaying the appropriate data.
The essence of this is captured in this minimal (pseudo) code:
// Action Creator: actions/data.js
import { fetchData } from '../utils/server';
export function fetch() {
fetchData();
}
// Server Action Creator: actions/data-server.js
import dispatcher from '../dispatcher';
export function receiveData(data) {
dispatcher.dispatch({
type: 'RECEIVE_DATA',
payload: data
});
}
// Web API Utility: utils/server.js
import request from 'request';
import { receiveData } from '../actions/data-server';
export function fetchData() {
request.get('https://api.com/v1/data', receiveData);
}
// Store: stores/store.js
import dispatcher from '../dipatcher';
import { EventEmitter } from 'events';
const CHANGE_EVENT = 'change';
let __data__ = [];
const Store = Object.assign({}, EventEmitter.prototype, {
emitChange () {
this.emit(CHANGE_EVENT);
},
addChangeListener (callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener (callback) {
this.removeListener(CHANGE_EVENT, callback);
},
getData () {
return __data__;
}
});
Store.dispatchToken = dispatcher.register(function (action) {
switch (action.type) {
case 'RECEIVE_DATA':
__data__ = action.payload;
Store.emitChange();
break;
}
});
export default Store;
// Controller View: components/List.jsx
import React from 'react';
import Store from '../stores/Store');
import { fetch } from '../actions/data';
var __getState = function () {
return {
data: Store.getData()
};
};
export default React.createClass({
getInitialState () {
return __getState();
},
componentWillMount () {
fetch();
},
componentDidMount () {
Store.addChangeListener(this._onChange);
},
componentWillUnMount () {
Store.removeChangeListener(this._onChange);
},
_onChange () {
this.setState( __getState() );
},
render () {
const { data } = this.state;
return (
<div className="list">
<ul>
{ data.map(dataItem => <li>{ dataItem.name }</li> )}
</ul>
</div>
);
}
});
Check out more detailed flux examples here.
I actually don't want a view to call for the data. In my opinion a view should be as stupid as possible...
It's good practice to distinguish between smart and dumb components. The idea is that smart components hold state and call actions, while the dumb components have no dependencies on the rest of the application. This creates a clear separation of concerns and allows for better reusability and testability of your components. Read more about it here.
Also there are some interesting alternatives besides flux. For instance there is redux. It uses a single immutable state Object (i.e. Store) where a reducer (pure function) is only allowed to modify application state by means of actions.
And definitely check out react-refetch if you're mostly fecthing and rendering read-only data from a server.