Redux
Redux is about two things
Managing a global data store also called state management
Promoting a uniform data flow
#State management
A state in our app can exist inside a component but also in a global object. Typically state that is only needed in one component should stay there. State that is used by more than one component is usually put in a global store. Another thing we need to think about is that at some point the state needs to be saved down, that's usually done by using performing an AJAX call towards an endpoint.
When using a state management pattern here are the concerns:
what should go in there
how do we ensure that all components are in agreement what the state is
how do we ensure that state changes happen in the right order, some changes are synchronous and some are asynchronous
Unidirectional data flow
A unidirectional data flow is about ensuring that data only flows in one direction. What do we mean by that? Let's try to exemplify it by the following example.
We have two components, one create component and one list component. When an item is created in the create component we want this information to reach the list component. We don't want them to talk directly but rather indirectly. We usually solve that by using a pub-sub, publish-subscribe pattern
We have our scenario and we are trying to define what should happen in what order:
User interaction, user enters values for a new item and presses a save button
The values are captured into a message and is being passed on, dispatched
A central store processes the message that leads to a change of its internal state
The central store then communicates out to all its listeners that the internal state has changed, one such listener is our list component
Why is this called unidirecitonal? Well the data in this case is only flowing in one direction. We start with a user interaction and we end up with another UI component being updated. What about other scenarios such as fetching initial data to our list component? Well in this case we might have the following flow:
list component asks for data from the central store by sending it a message
central store in turn sends a message that leads to the data being fetched from an endpoint via AJAX
when the data arrives the state of the central store is changed and an event is emitted that a listener, in this case the list component is listening to
As you can see we communicate with messages that ends up being interpreted and leads to a change of the state of our internal store. That change is broadcasted to a listener/s and the UI gets updated
Actions
An Action is a message that we need to pass on to our centralized store. It carries the intention of what we are trying to do as well as data, also called payload. An Action is usually represented as an object.
const action = { type: 'CREATE_ITEM', payload: 'my new item' };
The type is our intention and should be a verb saying what we are trying to achieve. The payload carries our data and can be a string or an object or whatever best represents our data.
#Action creator
It is quite common to use something called an action creator. It is simply a function that makes it easier for us to create our action object. It looks something like this:
const createItem = (newName) => ({ type: 'CREATE_ITEM', payload: { title: newName } });
Reducer
Follow me on Twitter, happy to take your suggestions on topics or improvements /Chris
A reducer is a function that is able to process our message, our Action. A reducer takes the existing state and applies the message on it. The end result is a new state. A reducer typically operates on a slice of state.
#Defining a reducer
A reducer typically looks like this:
function reducer(state = [], action) {
switch(action.type) {
case 'CREATE_ITEM': return [ ...state, { ... action.payload}];
case 'REMOVE_ITEM': return state.filter(item => item.id !== action.payload.id); default: return state; } }
Using the reducer is done by invoking it:
let id = 1;
const actionCreator = (title) => ({ type: 'CREATE_ITEM', payload: { id: counter++, title } })
let state = reducer([], actionCreator('item1'));
// [{ title: 'item1' }]
state = reducer([], actionCreator('item2'));
// [{ title: 'item1' }, { title: 'item2' }]
What we can see above is how we can use the reducer and take initial state, and apply an action to it. We also see how we can take an existing state and another action and simply create a new state which consists of the old state + action.
#Reducer Types
There are different types of reducers. We have shown a list reducer so far but it is possible to define reducers for:
lists
objects
primitives
#Object reducer
We have already showcase the list reducer so let's have a look at an object reducer.
The aim of an object reducer is simply to either load the object or update parts of it, if we are editing it for example. Let's show some code:
const reducer = (state = {}, action) => {
switch(action.type)
{ case 'LOAD_ITEM':
case 'UPDATE_ITEM': return { ...state, ...action.payload }
case 'REMOVE_ITEM': return null; } }
#Primitive reducer
This is quite an easy one. It looks like this if we are dealing with an integer:
const reducer = (state = 0, action) => {
switch(action.type) {
case 'INCREMENT': return state + 1;
case 'DECREMENT': return state -1; default: return state; } }