Migrate smoothly your Flux isomorphic app to Redux

Migrate smoothly your Flux isomorphic app to Redux

Flux, history reminders…

« Flux is the application architecture that Facebook uses for building client-side web applications. » That’s the definition of Flux on the Facebook website. So Flux is just a pattern, not a framework, that goes well with React, but not only. The model is focused on user interactions. Its main strength is the unidirectional data flow that enforces developers to be careful and ensures code consistency when application grows up.

Several libraries propose tools to implement Flux pattern easily. If no one stood out from the crowd at the beginning, now Redux, created by Dan Abramov, is clearly the one that the community have chosen as you can see below. Most Flux based React start kits you can find are based on Redux.

Redux popularity

At M6Web, our 6play web application is not designed with Redux, but we use Fluxible. Fluxible is another Flux library, developed by Yahoo. We chose it back in December 2014, when we started the project, because Fluxible was at the time one of the few tool designed for isomorphic applications. Moreover it was already used in production by Yahoo.

Why do we think Redux is a better choice

Even though Fluxible did get the job done, we are now willing to upgrade our application to Redux. Why?

Migrating a big app from Fluxible to Redux is crazy, isn’t it?

6play is a very big web application. How to migrate to Redux in a reasonable amount of time and without risk?

We were quite sure that Redux and Fluxible could work together. The goal would be to migrate gradually to Redux without having to remove Fluxible in one giant step. First of all, because we can’t mobilise enough resources to do this in a relatively short time. Secondly, we want to avoid a big deploy in production and potentially critical bugs (even though our application is well tested, there are always cases that we can’t control like memory load for example).

We tried it… And we succeeded! And this is quite simple.

First, we define the store configuration like other Redux application.

{% highlight javascript %} // configureStore.js

import {createStore, combineReducers, applyMiddleware, compose} from ‘redux’; import thunk from ‘redux-thunk’; import {canUseDOM} from ‘fbjs/lib/ExecutionEnvironment’;

import myReducer1 from ’./modules/myModule1/myModule1.reducer’; import myReducer2 from ’./modules/myModule2/myModule2.reducer’;

export default initialState => createStore( combineReducers({myReducer1, myReducer2}), initialState, compose( applyMiddleware([thunk]), canUseDOM && window.devToolsExtension ? window.devToolsExtension() : f => f ) ); {% endhighlight %}

Then we initialize Redux store in our server file. For isomorphic purposes, we have to serialize stores’ state and give it to the html so that client side can take control of the application with server’s data. So here, we build data for the client by combining Redux and Fluxible states.

{% highlight javascript %} // server.js

import {provideContext} from ‘fluxible-addons-react’; import {match, RouterContext} from ‘react-router’; import configureStore from ’./configureStore’;

processAppRequest() { // …

const fluxibleContext = FluxibleApp.createContext(); const reduxStore = configureStore(initialState);

match({routes: FluxibleApp.getComponent(), location: url}, (error, redirectLocation, routerState) => { // …

// Original Fluxible root element
const rootElement = React.createElement(
  provideContext(RouterContext, customContextTypes),
  {...routerState, context: fluxibleContext.getComponentContext()}
);

// Now with Redux
const markup = ReactDOMServer.renderToString(
  React.createElement(Provider, {store: reduxStore}, rootElement)
);

// Build state for client
const finalState = {
  ...FluxibleApp.dehydrate(fluxibleContext),
  reduxStoreState: reduxStore.getState()
};

// Then build the response layout with the markup and the whole state as usual
// ...

} } {% endhighlight %}

On client side, we do the opposite operation.

{% highlight javascript %} // client.js

import {provideContext} from ‘fluxible-addons-react’; import {Router, browserHistory} from ‘react-router’; import configureStore from ’./configureStore’;

const dehydratedState = window[stateVarName]; const reduxStore = configureStore(dehydratedState.reduxStoreState);

// Fluxible rehydrate its state app.rehydrate(dehydratedState, (error, fluxibleContext) => { // …

// Original Fluxible root element const rootElement = React.createElement(provideContext(Router, customContextTypes), { history: browserHistory, routes: app.getComponent(), context: fluxibleContext.getComponentContext() });

// Now with Redux ReactDOM.render( React.createElement(Provider, {store: reduxStore}, rootElement), document.getElementById(rootId) ); }); {% endhighlight %}

And that’s it! We can now use Redux in our component as usual, in combination with Fluxible. We can define actions and reducers for new features (instead of using Fluxible) but we can also transform progressively some Fluxible stores and actions into Redux flow, this is very easy. API requests stay in actions but data processing moves to reducers. Then data sorting and filtering logic in Fluxible stores moves to selectors.

Components connection to stores

Two files to rule them all

With Fluxible, we linked components with stores through connectToStore in the same file and exported only the connected component. But we think now it is a bad practice:

From now on, components are files named *.component.js and stores connections are in *.connector.js files in the same folder. We can link a component both with Redux and Fluxible stores.

{% highlight javascript %} // myComponent.connector.js

import MyComponent from ’./myComponent.component’;

// Stores import connectToStores from ‘fluxible-addons-react/connectToStores’; import {connect} from ‘react-redux’; import MyFluxibleStore from ’../stores/myFluxible.store’;

// Utils import {getSomeDataFromState} from ’../myModule.selectors’;

// Redux export const mapStateToProps = (state, props) => { return {dataFromRedux: getSomeDataFromState(state, props.myProps2)}; };

// Fluxible export default connectToStores( connect(mapStateToProps)(MyComponent), [MyFluxibleStore], (context, props) => ({ dataFromFluxible: context.getStore(MyFluxibleStore).getSomeData(props.myProps1) }) ); {% endhighlight %}

We export mapStateToProps function because in a few cases it contains logic that may be interesting to unit test.

Stores connections order

In this example, the link to Fluxible store is higher in components tree than the Redux one as we can see below.

Component tree

It means that if Redux state changes, the Fluxible wrapper component won’t be reloaded but in the reverse case, both Fluxible and Redux wrapper components will rerender. In most scenarios, it doesn’t matter. Connection order of Redux and Fluxible is significant in two situations:

{% highlight javascript %} // myComponent.connector.js

import MyComponent from ’./myComponent.component’;

// Stores import connectToStores from ‘fluxible-addons-react/connectToStores’; import {connect} from ‘react-redux’; import MyFluxibleStore from ’../stores/myFluxible.store’

// Utils import {getSomeDataFromState} from ’../myModule.selectors’;

// Fluxible wrapper depends on data from Redux state const MyComponentFluxibleConnector = connectToStores( MyComponent,
[MyFluxibleStore], (context, props) => ({ dataFromFluxible: context.getStore(MyFluxibleStore).getSomeDataFromReduxState(props.myPropsFromRedux) }) );

// Redux export const mapStateToProps = state => { return {myPropsFromRedux: getSomeDataFromState(state)}; };

export default connect(mapStateToProps)(MyComponentFluxibleConnector); {% endhighlight %}

If we watch carefully to those particular cases, we will succeed in our quest!

In a nutshell, Redux can easily work in addition to Fluxible (and certainly to other Flux libraries), most likely because of the lightness of its implementation. It is very convenient to upgrade smoothly a big application on Redux! But be aware that it is only a transitory situation, the final goal is to use only Redux. We wrote 50% less code with this upgrade, not bad… Developers are lazy, don’t forget this! If you have some feedback on Redux and/or Fluxible, don’t hesitate to share your experience with us :)