The concept of a Reducer became popular in JavaScript with the rise of Redux as state management solution for React . But no worries, you donât need to learn Redux to understand Reducers. Basically reducers are there to manage state in an application. For instance, if a user writes something in an HTML input field, the application has to manage this UI state (e.g. controlled components ).
Letâs dive into the implementation details: In essence, a reducer is a function which takes two arguments â the current state and an action â and returns based on both arguments a new state. In a pseudo function it could be expressed as:
As example, it would look like the following in JavaScript for the scenario of increasing a number by one:
Or defined as JavaScript arrow function, it would look the following way for the same logic:
In this case, the current state is an integer (e.g. count) and the reducer function increases the count by one. If we would rename the argument state to count , it may be more readable and approachable by newcomers to this concept. However, keep in mind that the count is still the state:
The reducer function is a pure function without any side-effects, which means that given the same input (e.g. state and action ), the expected output (e.g. newState ) will always be the same. This makes reducer functions the perfect fit for reasoning about state changes and testing them in isolation. You can repeat the same test with the same input as arguments and always expect the same output:
Thatâs the essence of a reducer function. However, we didnât touch the second argument of a reducer yet: the action. The action is normally defined as an object with a type property. Based on the type of the action, the reducer can perform conditional state transitions:
If the action type doesnât match any condition, we return the unchanged state. Testing a reducer function with multiple state transitions â given the same input, it will always return the same expected output â still holds true as mentioned before which is demonstrated in the following test cases:
However, more likely you will see a switch case statement in favor of if else statements in order to map multiple state transitions for a reducer function. The following reducer performs the same logic as before but expressed with a switch case statement:
In this scenario, the count itself is the state on which we are applying our state changes upon by increasing or decreasing the count. However, often you will not have a JavaScript primitive (e.g. integer for count) as state, but a complex JavaScript object. For instance, the count could be one property of our state object:
Donât worry if you donât understand immediately whatâs happening in the code here. Foremost, there are two important things to understand in general:
The state processed by a reducer function is immutable. That means the incoming state â coming in as argument â is never directly changed. Therefore the reducer function always has to return a new state object. If you havenât heard about immutability, you may want to check out the topic immutable data structures.
Since we know about the state being a immutable data structure, we can use the JavaScript spread operator to create a new state object from the incoming state and the part we want to change (e.g. count property). This way we ensure that the other properties that arenât touched from the incoming state object are still kept intact for the new state object.
Letâs see these two important points in code with another example where we want to change the last name of a person object with the following reducer function:
We could change the last name of a user the following way in a test environment:
You have seen that by using the JavaScript spread operator in our reducer function, we use all the properties from the current state object for the new state object but override specific properties (e.g. lastname ) for this new object. Thatâs why you will often see the spread operator for keeping state operation immutable (= state is not changed directly).
Also you have seen another aspect of a reducer function: An action provided for a reducer function can have an optional payload (e.g. lastname ) next to the mandatory action type property. The payload is additional information to perform the state transition. For instance, in our example the reducer wouldnât know the new last name of our person without the extra information.
Often the optional payload of an action is put into another generic payload property to keep the top-level of properties of an action object more general (.e.g { type, payload } ). Thatâs useful for having type and payload always separated side by side. For our previous code example, it would change the action into the following:
The reducer function would have to change too, because it has to dive one level deeper into the action:
Basically you have learned everything you need to know for reducers. They are used to perform state transitions from A to B with the help of actions that provide additional information. You can find reducer examples from this tutorial in this GitHub repository including tests. Here again everything in a nutshell:
Also check out this tutorial if you want to know how to use reducers in React with the useReducer hook .