Redux for the Perplexed

I was working at Facebook in 2015, I believe, when I asked my manager for a temporary transfer to a team that was working with React and Flux. Back then, looking at this new pattern, I was somewhere in between of being amazed and perplexed. Like, what is this thing, just why? Nowadays fewer projects use Flux since the invention of Redux, which is pretty similar to it, just simpler. If you feel the same way about it the way I felt about Flux, this article is written for you, to aid your entry into the world of this amazing way of application state management.

Intro

At its core, Redux is just a simple little library, maybe call it a design pattern, which will help you manage your application’s state. Things such as values inputted in a form, or the list of names shown in a table. It does this by storing all of the state into one giant, nested object. The state object is immutable, the view layer of your application cannot directly alter the model of the application, nor can one part of the model update another part of the model. All updates of the model are happening at a neatly collocated space, called the reducer function, and it always happens in a unidirectional, so called, dispatch cycle. If that all makes sense – congrats, you need not necessarily read any further. Check out the official Redux documentation and jump right into it. If however, you are still perplexed (like I was), keep reading…

Comparison with MVC

Tell me, do you know what Model-View-Controller a.k.a. MVC is? Think about it then keep reading.

To my understanding, it consists of (obviously) three things:

  1. Something about having an object which holds raw data, the primitives, also known as the model.
  2. Another thing which shows the model to the user, known as the view.
  3. Controller, which reacts to user’s actions and updates the model, which then updates the view via a set of listeners.

In MVC, data flows around from model to view, from view to controller, then from controller to model. And back again. In addition to that, one model object can subscribe to another model object, pretty much everyone can read, subscribe and alter the model, and so on. This can lead to a messy event propagation through your application state, including infinite loops and whatnot. Very complex to debug.

Redux solves the same problem as MVC does, except that it avoids the infinite loops and “everyone can update the model however they want” part. It’s concerned only with the model, and partially with the controller – the part where controller updates the model. It also makes sure that each state is well contained – just like a React component is – nothing outside of it can affect how it manages it’s data. So what does it have instead?

  1. It has a state, which is roughly equivalent to the MVC model – a plain, preferably immutable, JavaScript object.
  2. It has actions (similar to a controller), which describe events in the system that should trigger model updates.
  3. Finally, there is the reducer (also similar to a controller), which is simply a pure function that explains how an action affects the state.

The coupling between the model and model controller is much tighter here. This makes it much easier to encapsulate (in code) where and how does model change with each action. You no longer can have one model subscribe to another and trigger an avalanche of chained updates, including the dreaded infinite loop.

Before jumping into more theory, let’s take a look at some code…

Writing a reducer using Ducks

There are actually multiple styles around regarding how do you write Redux code. The one I’ll present here is the ducks format, which is rather collocated form of writing Redux. A “duck” is a file which contains three things – action types, action creators and a reducer:


// ./app/ducks/document.js

// 1) Action types usually are stored in a constant
// Prefixed by the Duck name.

const DOCUMENT_CHANGE_TITLE = 'document/changeTitle';

// 2) Action creator is a function that returns an action.

export function changeTitle(newTitle) {

  return {
    type: DOCUMENT_CHANGE_TITLE,
    newTitle,
  };
}

// 3) the meat of the duck - reducer.
const INITIAL_STATE = { 
  title: '',
};

export default function documentReducer(
  state = INITIAL_STATE,
  action, 
) {

  // Check whether this action affects our reducer.
  switch (action.type) {
    // If the action is one of our own, then yes.
    case DOCUMENT_CHANGE_TITLE:
      return {
        ...state,
        title: action.newTitle,
      };
    // Otherwise we don't want to change the state.
    default:
      return state;
  }
}

Alright, make sure to read the snippet above and understand as much as you can.

To put the above code into words – by Redux design – whenever something happens in your app (user enters a query, clicks a button etc) – you will fire off, or dispatch, an “action”. More about this later. Action is just an object with the type property. This action will then be sent to all the reducers. That’s important to notice. This is why our reducer has the switch statement – to filter out all other actions which are not meant for this reducer.

To go into details, let’s look at each of the parts individually:

  1. Here we specify which action types exist in this ducks file. Our has only one – the DOCUMENT_CHANGE_TITLE. Usually, they are prefixed with the duck name (“document”, you can also see this in the duck name – document.js) and suffixed by the action purpose (“change”, as this action is about updating a value) and target (“title”, as we want to change the title). By default, this constant is not exposed, but it can be made so if we desire other reducers to be able to react to this action event as well.
  2. The action creator is an exposed function. This is what external components (e.g. React) will use to communicate their intention with the reducer. This is how you trigger state updates. Simplest action creators just return a simple object with a type property, which hold one of the values from the action type constants. These functions don’t have side-effects – they don’t change anything outside of their scope (or at least not until they have been dispatched, in case of redux-thunk library, which will is an extension of Redux).
  3. Reducer is the glue which combines all different action types into one state object. It’s literally a reducer, in the functional programming sense, taking the previous state and next action event, and reduces them to the next state. It is important to notice here that the input state object is not modified – instead it is cloned into a new object by using the spread syntax (three dots).

Redux store

Once we have all these reducers / ducks written out, we need to combine them into a store.


// ./app/store.js

// 1. Import redux.
import { 
  createStore,
  combineReducers,
} from "redux";

// 2. Import our ducks
// The first one is our example above,
// others are left to your imagination.
import document from './ducks/document';
import notifications from './ducks/notifications';
import session from './ducks/session';

// 3. Export a store creating function.
export default function createMyStore() {
  return createStore(
    combineReducers({
      document,
      notifications,
      session,
    })
  );
}

Again, make sure you read and understood as much as you can.

To put the above code into simple words, we’ve just created a function that will help us create that large, nested, state/model object – also known as the store. We do this by taking a bunch of reducers and combining them into one. The store object has two things – not mentioned above – the getState and dispatch methods. Calling getState will produce an object that has three keys – title, content and notifications, each of which will hold its corresponding state object as described by each reducer.

So if anyone wanted to know what the current title is, they would have to access store.getState().document.title. First layer of that access pattern is always calling the store’s getState() method. Second layer is the reducer’s name – document. Everything else remaining is accessing the desired property from our document state – title. This is the case when using combineReducers such as above.

To go into details, let’s examine each of the steps:

  1. This basic example includes importing the createStore function and combineReducers function from the Redux library. There are others, for more advanced examples, such as applyMiddleware and compose etc. Thing is, they are all much simpler to understand once you know the basics. We will use these two functions later.
  2. Now we import our ducks / reducer functions. Each duck will later become part of the store, by using the Redux functions mentioned above.
  3. Meat of the store creation is combining the createStore and combineReducers functions. Former one creates the store – an object which has the getState method, for fetching the current state, and a dispatch method, for firing off new action events into the store’s reducers. Since createStore works with a single reducer only, we need to use the combineReducers method to combine our multiple reducers into one big reducer. Effectively in the store’s state, it will produce an object that will be keyed by the reducer name, and a value which will come from the reducer’s state.

Dispatch cycle

Finally, how does one communicate with Redux? When you need to render your view, the data is then fetched from the store using the getState method. When a user does something on the app that should affect the state, you will communicate with the store through it’s dispatch method. Usually, projects will initialize their store at the application bootstrap and keep it in a singleton. Then they will use a wrapper library, like react-redux to “connect” their components with the store (both dispatch and getState), so that it removes some boilerplate. But without going into depth of such libraries for different UI frameworks, in it’s core, this is what happens:


// ./app/application.js

import createMyStore from './store';
import { documentChangeTitle } from './ducks/document';
 
// 1. Initialize the store somewhere in your code, once:

const store = createMyStore();

// 2. Dispatch an action from your controller:

store.dispatch(documentChangeTitle('Hello World'));

// 3. Obtain the new state, from your view:

console.log(store.getState().document.title);

The example above has three parts, which are usually not all in the same file in a real world application.

  1. Initializing the store is as simple as calling the store creator function. You keep this store somewhere in a singleton so that the entire app can access it. Preferably you will use your framework’s extension library to manage the store, such as react-redux.
  2. Altering the state is possible only through the dispatch method. You basically construct your action object using an action creator (documentChangeTitle in this case), describing what’s changing, and then let the store know about it using the dispatch method. This will trigger all reducers in your application. Only the reducers which care about this action (document.js) will react to it – and as a result the internal state of the store will change to reflect this action.
  3. Getting the store’s state (model) is as simple as calling getState(). Then you navigate through the returned object’s layers to find the value you need. This is something an MVC view would do, most commonly.

The dispatch cycle is basically this pattern of dispatch, getState, dispatch, getState… There is no other way to change the store’s state, nor to retrieve it, than through these two methods. This is what makes the data neatly encapsulated and unidirectional. The actions flow from your application’s event handlers to the reducers, which update the store’s state, which causes the presentation layer to change, and all over again.

Conclusion

Hopefully by now you have a better idea on what Redux is, how it compares with MVC, what does it generally look like and you are no longer perplexed.

Finally, if you have any questions or doubts about Redux, I highly recommend going through the official Redux documents to expand on your knowledge at https://redux.js.org/api as well as feel free to shoot me an email at sreckotoroman@gmail.com

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s