Three ways to fetch data in React using Flux

Today we’ll investigate three approaches to fetching data, from the expensive and complex to the dead-simple.

1. Full featured, with the lot

Put your data-fetching function or action dispatcher on your component (the static fetchData method):

export default class UserList extends Component {
  static fetchData(params, dispatch) {
// this resulting action returns a promise, from our promise middleware
    return dispatch(UserActions.fetch(params.userid))
}
  render() {

You could also return a regular value, which Promise will handle just the same (but immediately). Now on pre-render we can call all matching routes with fetchData methods before rendering in React at all.

let { dispatch, getState } = store,
// set up your Redux store prior to running react-router
{ routes, params } = routerState,
// state returned from react-router's run() or match() methods
prefetchedData = {};
// Stores the results using route.name as the key, returned data as value
let dataPromise = Promise.all(
routes.filter(
route => typeof route.handler.fetchData === ‘function’
).map(
route => route.handler.fetchData(params, dispatch).then(
res => prefetchedData[route.name] = res
)
)
).then(() => prefetchedData);
// When this promise resolves (or rejects!), all fetchData values will be in prefetchedData, and also in your Redux store.

It’ll be available to your server render, so your initial render will be full of real data, which you can dehydrate (using getState()) into the DOM and have it instantly available on the client (when you rehydrate this state into your createStore()).

It’s the best, from a delivery perspective, but will take you time and you’ll hit many parts of your app activating these features.

See: Redux’s “Server Rendering” advice.


2. Moderate

A component can automatically fetch the data it needs on mount. It will load fresh data any and every time this component is mounted.

export default class UserList extends Component {
  componentDidMount() {
let { dispatch } = this.props;
    // When we display this component, fetch required data
dispatch(UserActions.fetch());
}
  render() {
let { session, playlists } = this.props;

It’s quick and dirty, but it works. You can prevent multiple data fetches by setting and checking against a static or scoped variable (add let isFetched = false; outside of the component). Keep in mind that without isFetched, if multiple components fetch same data, you’ll be fetching more than you need to.

Note, componentDidMount is only ever called on the client, never for server renders.


3. Cheap as Chips

Add an actor which simply fires dispatch(FETCH) when conditions are met (such as, when session auth is first available in your store). It’s as flexible as you need, detached from the presentation, and the concept is reusable.
(actors.js or actors/index.js)

export function playlists(store) {
let hasDispatched = false;
  store.subscribe(() => {
let { session} = store.getState();
    if (!hasDispatched && session.user) {
isDispatched = true;
      store.dispatch(PlaylistActions.fetch(‘playlists’))
}
})
}

Now, when we create our store: (app.js or client.js)

import * as Actors from './actors'
const store = createStore();
// activate each actor
for (let actor of Actors){
actor(store);
}

Hopefully this gives you some perspective — we all want the greatest universally rendered apps, but given real life constraints like time or developer output, it can be prudent to implement what you need over what you might need possibly some day. The first method is “best”, but requires a lot of supporting code throughout your app; the third method doesn’t even require your components dispatch FETCH actions.

If your goal is to build to MVP, launch, and iterate, you’ll probably be updating most of your bootstrap code as React/Redux/React-Router release new versions anyway. Cheap is good.

If you have a dev team of 10+ and you’re taking on the world, you can afford the complexity (and reap the benefits) of complex rendering setups.

These are just three (illustrative) ways to fetch store data. You’ll have to adapt to the specifics of how your application is set up. All examples are in ES6, assuming React version 0.14, Redux 3.3, and React Router 2.

Comments, suggestions and clarifications welcome. You can hit me up on Twitter or here at @grrowl.