12 months, 12 technologies: Understanding Redux
So, you hear about this neat little library called Redux. A lot of people in the React community use it, so you decide to check it out. As you start reading the README, you discover that it probably wouldn’t be much use to you. But you realize something else: it’s only 2kB. What is this library even doing? To find out what Redux does, we first have to understand functional programming.
An introduction to Redux
The Redux Core Concepts describe Redux as being very simple. At its core, Redux is just a pattern for dealing with state. It starts with the premise that you have one simple JavaScript object in your application that contains all the state in your application. Let’s say I have a door-application, it might have the following state:
[code language="js"]const Door = { open: false, };[/code]
How would I change this when working with this model? You could do something like this:
[code language="js"]Door.open = true;[/code]
This is a very straightforward way to change whether the door is currently open or not. The architecture Redux proposes is different. Instead of changing the door directly, we would do an action to open it. The actions could look like this:
[code language="js"]{ type: 'OPEN_DOOR' } { type: 'CLOSE_DOOR' }[/code]
After that, we need someone that knows how to handle these actions. To us humans it is clear that OPEN_DOOR
should set the open
property to true
and that CLOSE_DOOR
should set it to false
. But a computer needs to be told. In the Redux architecture, you can define a function to do this. This function is called a reducer:
[code language="js"]function doorReducer( previousState, action ) { const newState; switch ( action ) { case OPEN_DOOR: newState = { open: true }; break; case CLOSE_DOOR: newState = { open: false }; break; default: newState = state; break; } return newState; }[/code]
So now you can open a door:
[code language="js"]Door = doorReducer( Door, { type: 'OPEN_DOOR' } );[/code]
In an actual Redux-powered application, the call to reducer functions is handled by a store. This is an object that knows about the reducers. It also has a dispatch
function which can be used to send an action.
I can hear you thinking: why is this better than just doing Door.open = true
? It’s a lot more code. You also need to know which action to use. And a reducer could have errors. There seems to be no upside.
What’s the problem?
In a traditional application, a message and the result of the message are highly coupled. When I call a method on an object, I am responsible for all the side effects this method might have. Contrast this to the Redux architecture where actions and their reducers are decoupled.
For example, if the door doesn’t open instantly but it takes two seconds any calling function has to wait two seconds before it can do anything that depends on the open door. While this issue can be solved by working with Promise
objects or providing callbacks, it can lead to very hard to reproduce bugs.
Consider a scenario where two things happen at the same time. Firstly, an AJAX request has a response that refreshes some UI. Secondly, the user clicks a button before the UI is refreshed. Now we have an interesting scenario where we need to consider the order in which things happen. A bug can easily be present only if the user clicks before the response arrives. It is very hard to reproduce this locally. The response arrives instantly locally, so you can never beat it by clicking the button.
It’s common for two things to happen at the same time, even in a simple application. The example I mentioned above can already lead to bugs.
In a larger, more complex application this is much more pressing. A ton of things happen at once, which leads to more than just two events colliding with each other. In a giant JavaScript application, dozens of events might occur at the same time. Take an application like Slack, for example. While I am typing a message, dozens of people might send me a message.
All these insights are very intriguing, but where does Redux come from? When you look at the Prior Art · Redux in the Redux documentation, you see a mention of Elm . I decided to go one step further and look into the language Elm is based on: Haskell .
Why do I love functional programming so much?
When I started learning about Haskell from the “Programming in Haskell” book, I was instantly hooked. As someone who has been programming all his life, Haskell was a totally new experience for me. I never came in contact with a declarative language before. There is something magical about telling what needs to happen instead of what needs to be done.
In the introduction of the book, there is a part called “A taste of Haskell”, and oh boy did it taste good. In the book, there is an example about quicksort. The example might not make sense at first, but after some studying, you’ll get it:
[code language="js"]qsort [] = [] qsort (x: xs) = qsort smaller ++ [x] ++ qsort larger where smaller = [ a | a <- xs, a <= x ] larger = [ b | b <- xs, b > x ][/code]
This is as close to a mathematical definition of quicksort you can get in code. Let me deconstruct this. The line below tells us that qsort
applied to an empty list results in the empty list:
[code language="js"]qsort [] = [][/code]
The next one is a little harder. The part before the =
tells Haskell that x
is equal to the first element in the list. xs
will then be a list of all remaining elements in the list.
The result of qsort
is then defined as a qsorted list of all elements that are smaller than x
concatenated with x
and a list of all elements that are larger than x
.
[code language="js"]qsort (x: xs) = qsort smaller ++ [x] ++ qsort larger[/code]
If you look at the last part, you see how smaller
and larger
are defined. The parts within the brackets are called list comprehensions. [footnote1]. In this example, smaller
is defined as a list of all the elements in xs
that are smaller or equal to x
.
[code language="js"]where smaller = [ a | a <- xs, a <= x ] larger = [ b | b <- xs, b > x ][/code]
The book has examples of how you could evaluate this code. One thing that struck me, was how pure of a notation this is. You have here, right on the page defined what quick sort is. It is hard to make a mistake in the implementation because you can see at a glance if it fits the definition of quicksort.
Let’s compare this example to PHP and see what quick sort looks like in PHP. I used Google to find an example of quicksort in PHP . I’ve also put it in the WordPress coding standards format.
[code language="php"]function quick_sort( $array ) { $length = count( $array ); if ( $length <= 1 ) { return $array; } else { $pivot = $array[0]; $left = $right = array(); for ( $i = 1; $i < count( $array ); $i++ ) { if ( $array[ $i ] < $pivot ) { $left[] = $array[ $i ]; } else { $right[] = $array[ $i ]; } } return array_merge( quick_sort( $left ), array( $pivot ), quick_sort( $right ) ); } }[/code]
There are some major differences between the two versions. The main difference is that in PHP you are telling the computer what to do. In Haskell, we define what the result is. For example, in Haskell, we defined the result for the empty list as the empty list. In PHP we are explicitly telling PHP: count the items in this array. Then based on a number of items, maybe return the given array because it is already sorted.
The same goes for the rest of the algorithm. The for
line in PHP tells us: go through all the items in this array. For each item: check whether it’s smaller or larger, then put it in the relevant array. These messy details are hidden in Haskell. We don’t care how or when the array is looped, we only care about a correct result. This is how abstractions work, they give us the possibility to not think about details that don’t matter.
If you heard about this before, that is because this is the difference between declarative and imperative. Imperative, telling a computer what to do. Declarative, defining what we want.
So what does this have to do with Redux?
When I was researching this article, I first started by reading the motivations for Redux. In that text, there is one word that is really prominent. That word is “state”.
This brings me back to when I was learning Haskell. I already knew a little bit about functional languages. For example that a functional language has a lot of pure functions. The one thing that kept me wondering was, how do you do side effects if you cannot have side effects? So I peaked at the chapter about IO to find out. There was one quote that really struck me even though I didn’t understand it yet (emphasis mine):
In Haskell, an interactive program is viewed as pure function that takes the current “state of the world” as its argument, and produces a modified world as its result, in which the modified world reflects any side effects performed by the program.
Replace world
with state
and you have Redux. In Redux terminology this would be a reducer. The reducer is where all the power in Redux comes from. These are pure, unit testable functions, which are very easy to debug. An action is merely a way to make the right reducer ‘happen’. It’s a little bit of architecture to make your code easier to read.
These similarities are no coincidence. In Redux prior art, they explicitly mention Elm as a huge inspiration. Elm, in turn, is heavily based on Haskell. So, therefore, understanding Elm and Haskell is a huge benefit to understanding Redux.
Why not go all the way?
When researching these kinds of technologies, I always have the idea: “Why not go all the way?”. If you are using Redux, why not make it even purer and use Elm or Haskell. I think this idea comes from the academic in me that loves elegance and pureness. However, what this idea doesn’t include is that everything is a tradeoff.
Elm and Haskell are a lot more abstract and, therefore, require a lot more abstract thinking. Everyone is capable of abstract thinking in different capacities. So, a Haskell codebase is harder to understand for some part of the development community. Whereas a PHP codebase is much easier to understand.
I think the reason for this is the different paradigm. In a programming language that has a declarative paradigm, it’s harder to understand what is ‘actually’ happening. From a procedural point of view there is less magic. In PHP for example, what you see is what you get. I say file_get_contents
and that is what I get. I put in a filename and I get the contents of the file. In Elm, you would have to do a command that represents a file retrieval. Then you have to implement a message that handles the content of the file.
That’s why something like Redux exists. We want to write JavaScript because it is the most accessible language out there. Everything that can be written in JavaScript will be written in JavaScript. But we still want maintainable and well-architectured code. Redux makes this possible. It’s just JavaScript, and we follow certain rules to remain sane.
Conclusion
I would highly encourage everyone reading this to check out Redux and play with it. Even if you don’t use it for your projects immediately, it will definitely have an effect on how you write code. If you have not yet learned a new programming language this year, I would highly encourage you to have a look at Elm or Haskell. I can recommend the book Programming in Haskell which is very accessible, even if you have never written a single line of code in your life.
If this stuff interests you as much as it interests me, I would love to have you on my team.
This article is part of my series 12 months, 12 technologies.
Footnotes
- List comprehensions are actually discouraged by the Haskell style guide . It’s often possible to use a higher order list function instead.
- I also stripped away all comments for brevity.
Read more: 12 months, 12 technologies »
Coming up next!
-
Event
WordCamp Netherlands 2024
November 29 - 30, 2024 Team Yoast is at Sponsoring WordCamp Netherlands 2024! Click through to see who will be there, what we will do, and more! See where you can find us next » -
SEO webinar
Webinar: How to start with SEO (November 19, 2024)
19 November 2024 Learn how to start your SEO journey the right way with our free webinar. Get practical tips and answers to all your questions in the live Q&A! All Yoast SEO webinars »
2 Responses to 12 months, 12 technologies: Understanding Redux