Skip to content

Instantly share code, notes, and snippets.

@edrpls

edrpls/blog.md Secret

Created July 25, 2019 18:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save edrpls/b95b8fec7794b5fca02c5d26de4bb51d to your computer and use it in GitHub Desktop.
Save edrpls/b95b8fec7794b5fca02c5d26de4bb51d to your computer and use it in GitHub Desktop.
title published description tags
Migrating a large Flux app to Redux without everything falling apart
true
How to migrate a large React app from flux to redux without (much) hassle
react, redux, migration, flux

react to redux image

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.


Prerequisites

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

To easily connect React to Redux we will use the react-redux middleware, and of course, we will need Redux as well.

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 type or 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.

Sample App structure

The sample application we will use, has the following structure:

https://gist.github.com/e4fa7cfc2d80bd3ef85692d3e1ae7aa4

In this scenario, we will start by migrating the Clients section, and assume each one has their corresponding Flux stores and actions.

Creating the first reducer

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.

https://gist.github.com/2ff0caf2cdf1bd36028a560c1d95fa2d

The 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:

https://gist.github.com/c526f22149f4ec69502412b59dad5249

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.

Redux reducer with a coat of 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.

https://gist.github.com/fa64494fc1b0673a0c03ea1452064b54

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.

Migrating the first action

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:

https://gist.github.com/4a8f1d0d9452ae4ad77a9277578c722a

Let's add the equivalent action creator for Redux, at the bottom of the file,:

https://gist.github.com/d4fe967444eea8bb0015c6464bd49a26

If another section of the app needs to consume the Flux actions, they can be imported like:

https://gist.github.com/942990cca47d3871836d8b4cfa15e53e

And the Redux actions can be imported without interfering with Flux:

https://gist.github.com/77a35bddb23575a5241c3969beaa488c

After all your components start using the new reducer, the old Flux actions can be either removed or commented.

Migrating an async action

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:

https://gist.github.com/ce3b96f8703f22d6a30cd53770e55eb9

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.

Connecting the stores

First, let's connect Redux to the app's entry point:

https://gist.github.com/6ce93b89b744e7cd0eddd7aecca98e69

Connecting the component

Now that the application is aware of Redux, we need the app to handle the new store and actions:

https://gist.github.com/b7567bdb6a6f57413dab830b8b249ef7

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.

Look ma! No Flux!

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.

https://gist.github.com/0173cfe64568996f8fb2906a764da306

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:

https://gist.github.com/1ae73fc539e315cf3bcdc7cb6f8fbf87

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!

Conclusion

  • 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 createStore we can create a Flux store that handles both Redux and Flux actions.


Thanks to GitHub user vivek3003 for the createFluxStore function and initial approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment