Becoming Familiar with Redux
This post was released on jscrambler.com June 30th 2017.
Redux has become an incredibly powerful tool for managing state in JavaScript applications. Redux is usually partnered with ReactJS but it can be integrated wherever one wishes to use it. There have also been a number of libraries created for Redux integration, such as ngRx for Angular. Regardless of how Redux is being implemented the core concepts behind it will always be the same. Today, we’re going to get a taste of how Redux works and take a look at the core concepts of the framework. By the end we’ll have a low-level counter application.
Core Concepts of Redux
"Immutable State Tree"
"State is Read-only"
An application’s store is read-only. It can only be changed by dispatching actions objects.
store.dispatch({
type: 'INCREMENT'
});
Soon we will see how to use action objects in better detail. Right now, it’s important to understand that enforcing a read-only state eliminates the need to worry about outside factors causing a mutation in application data, and that is just wonderful.
"Changes are made with pure functions"
To successfully update an application state, we must use a redux <em>reducer</em>, which will be used with JavaScript’s reduce
function to create the next application state. The reducer function is a pure function.
A pure function is a function that will always map the same input to the same output. They are essential to building immutable JavaScript applications, as they allow developers to look at the code and easily determine the result of a function. All a developer needs to do is take a look at the Redux-style reducer function, and the action object that is being dispatch. For those interested in truly learning more about reduce and why it’s one of the most powerful functions JavaScript has to offer I recommend checking out this link.
Now that we know the core concepts let’s dive into a sample application built using create-react-app
. The code for this project can be found at this GitHub repository
The first function we want to take a look at is our reducer function. Redux users will spend a lot of time using reducer functions so it’s important to understand how they work.
// index.js
const reducer = (state = 0, action) => {
switch(action.type) {
case ‘INCREMENT’:
return state + 1;
case ‘DECREMENT’:
return state - 1;
default: return state;
}
The reducer function takes two arguments: state
and action
. The state argument is the state of our application when an action was triggered. Because ES6 is so awesome we can set a default value for our state. In our case, the state currently is 0
. In addition to our previous state, the reducer also takes the action objects mentioned earlier.
Actions objects must have a type
property but may contain others properties such as payload
. The type
property is essential as it is what our reducer uses to determine what update it should make to the state. To accomplish this, reducer functions include switch... case
statements when updating state. Taking a look at this statement, we will see that it will switch on an action’s type property.
Specifically, it will add 1 to the counter when an action with a type of INCREMENT
is passed in, while DECREMENT
will subtract the counter by 1. In both instances, the result will be a new state object.
An application’s store can only be updated by dispatching action objects. To perform this we need to use the store.dispatch
method. Our example has the following code for a Counter
component.
// index.js
const Counter = ({value}) => {
return (
<div>
<h3>Counter</h3>
<button onClick={() => store.dispatch({ type: 'INCREMENT'})}>+</button>
{value}
</div>
)
}
Inside the return
statement for this component we see the store.dispatch
method being passed into react’s onClick
handler. Each time a button is clicked, our store will be updated accordingly. Following this we see the expression {value}
that will evaluate and render our application’s state, which in this case is the number of times the button has been clicked.
Final Touches
In order for all of this to work we need to use Redux’s createStore()
method. createStore
needs to be imported from the Redux library and it takes a reducer function. Our store
declaration should look like this.
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
const reducer = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT': return state + 1;
case 'DECREMENT': return state - 1;
default: return state;
}
}
const store = createStore(reducer);
/** counter component **/
const Counter = ({value}) => {
return (
<div>
<h3>Counter</h3>
<button onClick={() => store.dispatch({ type: 'INCREMENT'})}>+</button>
{value}
</div>
)
}
Now that our store has been created we need to use two more store methods: getState
and subscribe
. We’re going to use getState()
to populate the value property that is passed into the Counter
component by simply passing it into our <Counter />
tag.
// index.js
// Below the <Counter /> component...
const render = () => {
ReactDOM.render(
<Counter value={store.getState()} />,
document.getElementById('root')
)
}
This will give our component access to the state, but we still need to subscribe to our store. If we don’t our application won’t know when any state changes happen. In this example we’re going to subscribe to our render
const like so.
// index.js
store.subscribe(render);
It is important to note that there are libraries like react-redux which provide users with a number of tools that make react-redux integration a breeze. Nonetheless, we have successfully created our first redux counter app! The final code sample for our index.js
file should look like the following:
//index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
const reducer = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT': return state + 1;
case 'DECREMENT': return state - 1;
default: return state;
}
}
const store = createStore(reducer);
const Counter = ({value}) => {
return (
<div>
<h3>Counter</h3>
<button onClick={() => store.dispatch({ type: 'INCREMENT'})}>+</button>
{value}
</div>
)
}
const render = () => {
ReactDOM.render(
<Counter value={store.getState()} />,
document.getElementById('root')
)
}
store.subscribe(render);
render();
You made it!
Congratulations! You successfully worked your way through a reactJS application with Redux architecture. Hopefully by now, you have gained a bit of knowledge regarding how Redux works. In the meantime, I encourage those interested in Redux to check out the following links as they are great sources of information.