This post was originally published in GumGum’s tech blog.
We are big fans of React at GumGum. In fact, most of our apps have been built with it and almost all of them use Redux as well.
However, it wasn’t always like this. One of our first applications was built with Flux and while it works perfectly fine, there is some degree of context switching fatigue, especially for engineers working on newer Redux apps. Also, being unfamiliar with Flux can allow for some bugs when the store is read on component mount but not updated again afterwards. Since Redux passes the state as props, we can be sure that the information we’re reading from the store is always up to date. And last but not least, Redux’s implementation is only cumbersome the first time (as we will see in the next sections), while Flux requires adding a store listener to components as well as ensuring removal of said listener when unmounting the component.
This application is widely used internally and also by some of our clients, so trying to migrate it all at once would be quite the challenge. Doing it in one go would also require a lot of coding hours that would prevent us from developing new features (and an awful Pull Request for anyone to review). So, we decided to migrate the app slowly, whenever there’s free time from the usual new features and paying technical debt.
If you are like me and remained confused on how to migrate from Flux to Redux after reading the Redux documentation, you have come to the right place to learn how to do it.
This approach will help you migrate a section of a React app to Redux reducers and actions, while other sections still use your old Flux store.
There are some libraries that make using Redux with React much easier, so let’s go ahead and install them. These will probably be different depending on your project’s structure, and some may not even be needed.
In our example application, we use react-router, so we will need to connect the router props to pass them along with the store. This can be accomplished by using the react-router-redux middleware (we’re using react-router v3, so if your project uses v4, use connected-react-router instead).
Finally, our Flux stores perform many requests to the server, but since Redux actions are not asynchronous by default, we will use the redux-thunk middleware to allow this behavior. You could use something fancier if needed, but this simple middleware is more than enough for our purposes.
If you want to install all of that in a single line, try:
npm -i redux react-redux react-router-redux redux-thunk
This tutorial assumes your project has a working Flux store.
A bridge between stores
Now that we have installed the required dependencies, we need a way for our app to handle both Redux and Flux’ action calls. To do this, we will copy a simplified version of Redux
createStore and change it so that it handles objects including either
actionType properties for Redux and Flux respectively.
You can go ahead and copy this createFluxStore file to save time, but be aware that it uses lodash’s
isPlainObject, so if you don't use it in your project, just delete line 4 and 158 to 162, and everything should still work fine.
The sample application we will use, has the following structure:
In this scenario, we will start by migrating the Clients section, and assume each one has their corresponding Flux stores and actions.
Our clients section is rather simple, it displays a list of clients where the sorting can be reversed.
The store is using a slightly old syntax, but should be understandable enough:
Note: error handling was omitted for brevity.
getClients function is async, so this will not translate nicely to Redux, since the reducer should be a pure function. (this means having no side effects elsewhere - ie. an async request). It should just be an input and an output, but more on that later.
The sorting function on the other hand, doesn’t have any side effects and therefore, fits nicely with the reducer:
Great, our first reducer! The problem now is that we are not handling the server request (yet), and the reducer is not connected to the app (yet).
Next, we will connect the brand new reducer to the flux store.
At this point, the Flux store and Redux reducer operate independently of each other, so this is the time to use the
createFluxStore function to connect both. With this, actions intended for either store will be handled by the corresponding store, and both will share the same data origin. One downside of this implementation is that even though Flux uses Redux as the origin of its state, both will have a copy of the object.
We need to make a few changes to the ClientStore to read the state from Redux.
The first change is creating the ClientStore as an instance of EventEmitter instead of an instance of Store. This step will vary from project to project, and may not even be necessary.
With this store, we can get the state from the Redux reducer, start to move each function from flux to redux, and have both stores working without having to stop one or the other.
This might seem a bit overkill for our simple app, where we can take the risk of having both our actions broken, while we make the switch to Redux, but on an application with ten or more methods and stores, you would want all Flux methods working while migrating the others.
You can play around with this setup to go further and have the store update when Redux updates. I haven’t found that necessary because I usually work on a single piece of the store or method and migrate it to Redux on all the components that use it.
The first action we will migrate is the one that reverses the order of the results. This one is easy because there are no side effects, everything happens synchronously.
Our ClientActions file looks like this before migrating to Redux:
Let’s add the equivalent action creator for Redux, at the bottom of the file,:
If another section of the app needs to consume the Flux actions, they can be imported like:
And the Redux actions can be imported without interfering with Flux:
After all your components start using the new reducer, the old Flux actions can be either removed or commented.
To perform async actions with Redux, we will need to use the redux-thunk middleware. We will see how to connect Redux to our app in the next section, but first, let’s add the server request to get the list of clients, by adding this action creator to ClientActions.js:
Now our Flux store and actions have their counterpart in Redux!
Unfortunately, our components still don’t know anything about Redux or the reducer yet, so on the next section we will connect it to the app.
First, let’s connect Redux to the app’s entry point:
Connecting the component
Now that the application is aware of Redux, we need the app to handle the new store and actions:
By setting our app this way, we can pass the specific state and actions that each route will need. In some cases you will even find that your components can become stateless as they always receive the new state from the store.
Another thing to note is that we export our component twice, the default export requires a Redux store and its actions, while the other export is not connected, this helps us test the component as it lets us pass the state and props we need to instead of having to mock the whole Redux store. Testing is a topic best left for a different post.
Be aware that how you connect it might change depending on the react-router version your app uses.
Now that we are almost done migrating the Clients section, the last step is to use the Redux actions in our components instead of the old Flux actions.
Currently our component stores the clients in the state, and listens for Flux store changes, but it’s now using the reducer function from props to toggle the sorting.
Now that the component works with both Redux and Flux actions, let’s add the next one and remove all Flux related stuff, by using the props that we previously passed on the parent component:
As you can see, our component is simpler now that it gets everything from the props, and it only gets the specific data needed instead of having to call the whole store.
And that’s it, our first section has been migrated. We can now clean it up and remove all references to the old Flux methods (if no other component is still using them), and submit this for a pull request and work on the next section for the next sprint!
- Migrating a large react store is no easy task, but it can be done with just a few changes in gradual steps without breaking the whole functionality of an application.
- A variety of 3rd party libraries can help us integrate Redux and React, and by using a modified copy of Redux’s
createStorewe can create a Flux store that handles both Redux and Flux actions.
Thanks to GitHub user vivek3003 for the
createFluxStore function and initial approach.
Originally published at http://github.com.