Bulletproof React: Understanding The Functional Reactive Approach
Tags: react, functional, redux, javascript
Published: 2020-04-21 20:39:19

The principles of functional programming are becoming more in fashion every day. More and more traditionally imperative languages are implementing lambda functions, immutability and lazy evaluation. It's exciting to see, and even more encouraging to see that React is at the forefront of these changes.

React has always encouraged functional principles in some capacity; Redux has long been the most popular approach to building large scale apps. However, the advent of React hooks has made it clear that this preference for functional over imperative is very intentional, and it's here to stay. With all that said, I still hear the complaint that Redux is confusing, or seems "magical". There are also plenty of developers who think Redux is made obsolete by React's Context API, and while there's some truth to this, there are still some huge benefits to be gained by using the Redux approach, so I'd like to dedicate a post to demystifying just how it works and to outlining those benefits.

The most obvious benefit of using Redux would be that it moves all of your app state to a single source of truth, making it much easier to ensure that components stay in sync with one another. But there's more. Let's start by laying out all the key components of the Redux architecture.

![Redux Diagram](https://dev-to-uploads.s3.amazonaws.com/i/swzkvy0624myne44uldn.png)*Notice there is no 'store' entity in the diagram because the store is a transient value passed to the view from the reducer.*

## The Store

At the core of everything in a Redux app is the store. It's easy to think of the store as a container for all of your state which you can update, but the store is in fact immutable. It's a value passed through your app just like arguments to a function, and the only way to "change" the value is to call the function again with different arguments.

To better visualise this, let's create a very simple functional reactive app in JavaScript.

```javascript
// <button id="myButton"></button> defined in HTML

function myApp(state) {
  function dispatch() {
    myApp(state + 1);
  }

  const btn = document.getElementById('myButton');
  btn.innerHTML = state;
  btn.onclick = dispatch;
}

myApp(0);
```

We define our app as a function `myApp` which accepts our state as its argument. Within the context of `myApp` we define a closure called `dispatch`, which simply calls `myApp` again with updated state (the previous state + 1). We then use our state as the button's text label, and bind `dispatch` to the button's `onclick` listener. Finally, we bootstrap the app with a starting state value of 0. Now every time we click the button, its value will increase by 1 as `myApp` reruns with the updated state.

Simple, right? There's no magic here - this is functional reactive programming in its most basic form.

To bring it back to Redux, the `state` argument in our example would be the store in Redux. It's immutable - or more to the point, mutating it would have no effect because the app has already consumed it and finished running - and we have to use a dispatcher function to make changes to it. Redux also exposes a `dispatch` function which we either pass down to components via props, or we use the react-redux higher-order component `connect` to avoid props drilling. However, Redux's dispatcher function doesn't directly rerun the app, but the extra step is part of what makes it so powerful.

## Actions And The Reducer

When the `dispatch` function is called following a user interaction, it is passed an _action_. An action consists of a _type_ and a _payload_. This action is then passed through a _reducer function_. This is where the magic happens. The following is a simple example of a reducer function:

```javascript
const initialState = 0;

function reducer(state = initialState, action) {
  switch (action.type) {
    case 'ADD':
      return state + action.payload;
    case 'SUBTRACT':
      return state - action.payload;
    default:
      return state;
  }
}
```

Our reducer function accepts two arguments: the current state and the action passed to the dispatcher function. We check the action type and apply a transformation based on it. If the type is `ADD`, we return the current state plus the action payload; if the type is `SUBTRACT`, we return the current state minus the action payload. This returned value will become the app's new state.

```javascript
const myAddAction = {
  type: 'ADD',
  payload: 3
};

reducer(5, myAddAction); // This would perform 5 + 3 to return 8
```

Transforming our state using a reducer function means the state can only be transformed in a finite number of ways, which are all immediately visible when you view this function. No matter what we do, we can't multiply or divide the state without adding a new case to the reducer's switch statement. This is very powerful: no more tracking down where a logic error is coming from. If a state update happens it **must** be happening in the reducer function; the only question is where the dispatcher function was called from, which is easy to track down using a stack trace.

## Side Effects

It's time to go a little deeper into functional terminology (but only a little). Our app is now more deterministic thanks to all state updates being centralised in one function. However, how will our app communicate with the outside world?

In functional programming, any computation which doesn't consist of a function returning an expression based solely on its arguments is called a _side effect_. An app without side effects is useless; at the very least we need a way for our app to receive input and give output, and since both of these things rely on conditions being met in the outside world (e.g. the code being run in a browser with a DOM API for us to interact with) they would be considered side effects. However, just because our apps rely on side effects doesn't mean we should pretend they don't exist. Thinking proactively about where the side effects in your app are allows you to reduce the number of them you create, and to manage them safely.

Thankfully, React deals with IO for us and allows us to write pure computations safely behind the abstraction of the virtual DOM, but what if we want to get some data from a remote API over HTTP? Typically we'd just place this in a `useEffect` hook in one of our components, but this is less than ideal. For example, what if we have two of the same component on one page, and both of the instances perform the HTTP request? One of them would be completely redundant. We can program around this using finicky conditionals, but who wants that? Wouldn't it be the icing on the cake to not have to go through the ordeal?

We can solve this by using a Redux middleware. A middleware sits between the dispatcher function and the reducer function. An interaction causes `dispatch` to be called with an action; the action is then passed through any middlewares we set up, before finally reaching the reducer.

Let's say we're building an app which includes a list of users. On the initial page load we might dispatch an action to fetch the list of users from an API:

```javascript
{ type: 'FETCH_USERS' }
```

This isn't an action type which is recognised by the reducer, so it won't trigger a state update. Instead, we tell a middleware to wait for any actions with a type of `FETCH_USERS` and then perform a get request to the remote API. When a response comes back, the middleware calls the dispatcher function again with a new action:

```javascript
{
  type: 'SET_USERS',
  payload: users // 'users' is the response body
}
```

This subsequent `SET_USERS` action is picked up by the reducer and the app reruns with the new state which includes the fetched list of users. No searching for the component responsible for fetching a piece of data, we know it always happens in a middleware.

The most popular Redux middleware libraries are redux-saga and redux-thunk. They use very different approaches but both have their pros and cons.

## In Summary

So what have we gained? In short, transparency and determinism. Each aspect of our app is now clearly defined and has a dedicated place. The view is handled by React, but we can now be sure that it is composed of only pure functions which receive their props and return markup. **All** state transformations are triggered by actions and performed by the reducer function. All side effects (besides IO which is handled by React) are isolated within middlewares where nothing else depends on their success.

Using this approach, our apps can scale indefinitely with minimal runtime errors and without logic errors becoming impossible to track down and manage.