tonygo_
NUIT

🍕Refactoring reducers in React application (part 1)

How to refactor old reducers with createSlice & createAsyncThunk (redux toolkit)

Redux - React - Refactoring - 2020-05-15

This my first article on this blog, the feedbacks are very welcomed :)

A bit of context …

My manager allows me to lead the development of one feature, scheduled for the end of the quarter. Like I'm a newcomer I just began to be familiar with the codebase at this moment. Once functional specifications were written, I started to dig into our react/redux application to know where I should start. Here we go …

Before you read this ...

What is necessary to read this post :

  • Basic knowledge of redux (reducers, middleware, thunk)
  • Basic knowledge of react / react-redux

What this article won’t show :

  • Configure redux store
  • How middleware works in details

Read the code

My mission is to implement new components / a new user interface for :

  • displaying new data to the user
  • allowing users to interact with data (create, update, delete)

Nothing new my dear Michel!

This feature is an add-on for an existing view of our application. So this update will impact drastically :

  • reducer which store the actual data
  • components related to this view

Let see the store first.

A - Store

I choose to inspect the store at first. Why? Because the store is the application’s heart and those reducers arteries.

The main issue I saw is the multiplication of abstractions around asynchronous actions :

  • one to wrap fetch method and return JSON object (1)
  • another to get lifecycle actions (pending, fulfilled, rejected) (2)
  • an other to dispatch lifecycle actions (2) according to 1 (we call it thunk action)

If you’re familiar with react / redux based applications, you probably know all the steps necessary when you want to add a new action :

  • create action types constants
  • create an action creator function
  • add cases in reducers
  • create selectors

Those two points (too many abstractions and steps) make development slower, decreased developer experience, and increased risk of bugs.

Now we’ll take a look at components …

B - Components

Let’s dig a bit into our components. I found that some side effects are handled directly in class components like this :

handleSave = async () => {
    await this.setState({ isFetching: true})
    const { errors } = await this.props.saveEntity(...) // thunk
    await this.setState({ isFetching: false})

    if (errors) {
        this.props.notify('Entity not saved') // action redux
    } else {
        this.props.notify('Entity saved') // action redux
    }
}

At first, you can ask what is wrong ? Worse! Maybe you’ll find this code not bad at all because you’ll find all the logic at the same place. So you could make a short cut in your mind like: “easy to read, easy to update …”, but not really.

If you look again at this code you’ll see that the component manages:

  • the API call (by dispatching a thunk action)
  • the loading boolean
  • the notification call

Now imagine, what if we want to close a modal. With the current we'll get :

if (errors) {
  this.props.notify("Entity not saved")
} else {
  this.props.notify("Entity saved")
  this.props.closeModal() // new action redux
}

Do you see the trick? What if our application should handle 6 or 7 side effects? Could you keep all those in your component? Each time you’ll add new behavior you’ll make your components/methods heavier than before. It’s not scalable at all.

Note: a common practice is to make your components dummy as possible!

The other fact is that you keep some important data in your component as isFetching boolean or errors. Then other components cannot take profit from those data.

The last sad part, in my opinion, is the usage of async/await in this class's method: this.setState or redux actions are already asynchronous by nature. So by adding an await in front of actions or setState, you try to hack the original behavior of those tools. And if you have to hack experienced tools like react or redux it’s maybe that you have missed something.

The hardest part is to explain to people why they have not to use async/await in front of this.setState while it "works" in practice 🤦🏻‍♂️.

To summarize this component method have too big ownership, it probably should share this ownership with reducers and use tools in the right way.

Rethink reducers/component mix

The store should be the only place where data will be read or write. Components trig actions and subscribe to reducers data. In this way, components are dummy as possible.

In my opinion redux should be used like a “synchronous pub/sub” (Check the video of Yazan Alaboudi if you want more details on this):

  • Component dispatch actions which contain business logic
  • Each reducer which subscribed to this action will be triggered and update data
  • Each component which subscribed those data reducers will be update

redux schema

In the next post, we will talk about how implementation it.

Back to articles
tony-gorez

Hi 👋 ! My name is Tony, Im a software developer. As software engineering is about learning again and again, I decided to put all the stuff that I'll learn here !