Intro to Redux

Aron Adler
Engineering at FundApps
7 min readAug 24, 2018

--

In my first article for the FundApps Engineering blog, I’d like to write up a talk I gave a while ago, giving a quick introduction to Redux.

This article assumes you are familiar with modern JavaScript and React.

React Recap

Before diving into Redux let’s quickly recap what React is.

React is a popular view library for the web. It is generally used with a new syntax called JSX, which stands for “JavaScript XML”. JSX allows you to embed XML-like markup into your JavaScript code, making it a much more natural way of creating user interfaces.

React is also unidirectional. This means that — unlike for example with Angular — data can only flow from parent components to child components and not the other way round. Child components can only affect higher-level data or state if given a function that can do that by a parent component. They cannot change anything happening on a higher level.

See the example below.

Here is a simple Counter component. Its state object contains a single count property that can be either incremented or decremented by the two eponymous methods.

In addition to displaying the current count, our Counter’s render method also displays incrementer and decrementer buttons. As you can see we need to pass the increment/decrement methods to the button components from the same component that the state is contained in.

But — I hear you ask — why can’t we just make the buttons change the state directly, by doing something like this:

I.e. why can’t we just change the count property on the props object?

It is a nice dream, but unfortunately that throws the nasty error: Cannot add property count, object is not extensible. The only way to change Counter's state is by using a method from Counter itself.

This is what unidirectionality means. The flow of data only goes down, never up. A child component can never change a parent’s state. Although there are very good reasons for this, it can make it really difficult to change state in large React apps, when you have lots of deeply nested components. If you want a deeply nested button to change the top-level state you are going to have to pass the state-changing method from the top-level component all the way down to the lowly button, through potentially dozens of intermediate components — something known as “prop drilling”.

This is where Redux comes in.

Redux: what is it

Redux is a solution to this problem. It is a way of managing state in React — and other view libraries — in a manageable and predictable way, without unnatural amounts of prop drilling.

Structure

Redux consists of a few different parts: actions, reducers, the store, and connected (or “smart”) components.

  • Actions represent discrete, well, actions that change your application’s state.
  • Reducers determine how your application state changes in response to each action.
  • The store actually contains your application state and your reducer(s), plus it makes your state available to connected components.
  • Connected components hook into the store and can dispatch actions to it

Actions

An action is an object that contains at least one property that specifies what kind of action it is. By convention this is usually made to be the type property. Action objects can also contain additional properties containing data relevant to the state change they will cause.

Action creators are functions that return objects, this is useful when you want to create actions that can contain arbitrary data, e.g. if you want to create an "INCREMENT_BY" action that increments the count by an arbitrary amount – in that case you need the action to contain the number specified in your action creator via a parameter.

These actions will get passed to a reducer, which is what actually determines how your action will affect the application state.

Reducers

Reducers are functions that do one thing: given the current state and an action, they return the new state. They determine how your application changes in response to each action.

They usually contain just one switch statement on the action’s type property with a case for every possible action type. In each case they then return the modified state – potentially extracting other data from the action in the process.

For complex tree-like application states, it can make more sense to have separate reducers, each worrying about only part of the state tree. Redux allows for reducers to be composed in this way using the combineReducers function, which would look like so:

Store

The store is what actually holds the application state. It is constructed by taking the reducer plus some optional middleware (more about which later). It is then passed into the React Redux module’s Provider component, which makes it available to all descendant components via the React Context API.

Dispatching an action to the store is what makes its state change. You can trigger an action to be dispatched manually by calling store.dispatch(action) – although in practice you will never need to do this because you will only dispatch actions from inside your connected components. More on this later on.

Connected React components (via React Context API)

The above is all very nice and abstract, but what does it actually mean for you trying to build a React app? Well this is where connected or “smart” components come in.

Redux provides wrappers that can wrap a dumb function and hook it up to the store, allowing it to both get state data and dispatch actions. This is the connect function. It is generally used as in the snippet below.

Let’s explain what’s going on here.

First you have Counter, which is a simple component that knows nothing about the outside world. All it knows is that it takes 3 props: count, which is the current count to display; and increment and decrement, which are functions that should respectively increment and decrement the count. However these are just props provided to it by its parent. The component doesn't know or care how they are implemented.

Then there is mapStateToProps. This is a function that takes the current store's state and maps it to props that the component expects to receive. In our case all it retrieves from the state is its count property, which it passes on to its child component as displayCount. However this could include more serious transformation logic, like for example:

state => ({
displayCount: state.count,
squareOfCount: state.count ** 2
})

Finally, mapDispatchToProps is a function that maps the component's functional props to action creators. It takes one parameter, dispatch, which should get called with an action every time you want to, well, dispatch an action. You can pass parameters by just adding them in like so:

incrementBy: count => dispatch(incrementByAC(count))

The result of calling connect(mapStateToProps, mapDispatchToProps) is a smart component that can wrap a dumb component like Counter. This will connect it to the store and action creators in exactly the way you've specified.

Structure summarised

To summarise:

  • Actions are objects — containing at least a type property – that represent atomic changes in the state of your application
  • Reducers are functions that take in an action and the current state of your application and return the new state
  • The store is where the core of your application lives, it is constructed by taking in your reducer and any middleware you may have (see next section)
  • Connected components are where you can actually use your application state’s properties in your React application, as well as trigger state changes through dispatching actions

Middleware

You can also add middleware to Redux. These are functions that can do something with an action before it reaches the reducer. A commonly used one is Redux Thunk, used to trigger actions when an asynchronous function completes. Another handy one is the Redux DevTools extension, which allows you to view and manipulate your Redux store from a browser extension; it will make debugging your Redux setup much easier.

Why use it

Hopefully by this point you have some understanding of what Redux is and how to use it. Seeing how it works may also already have given you an idea of why you should use it. But in case you haven’t, let’s summarise them here.

  • Redux lets you properly manage state in React apps without having to engage in prop drilling.
  • It gives you highly predictable — and easily testable — state transitions.
  • When using the Redux DevTools extension, you can use the time-travelling debugger to easily debug your state change logic.

Nothing is without negatives however, and Redux is no exception. In my opinion these are:

  • There is a lot of boilerplate you have to write before you can get started. For example even for a really simple app you need to set up your store, reducer, actions and connected components — which themselves include state and dispatch mappings.
  • It tends to be quite verbose, with a lot of code to write in several different places to add functionality.
  • Because the type and contents of your action is tightly coupled to your reducer and connected components, making even a small change means updating a whole bunch of code in several different places. Without a good typing system, like TypeScript for example, it can be really difficult to keep track of what needs to be updated in response to a small change in one place.

In conclusion

Hopefully this post has given you enough for you to be able to get started building your first Redux app. Have fun doing so, and don’t forget you can always read the docs when you get stuck!

--

--