This site runs best with JavaScript enabled.

Lets build Redux in 5 minutes

Shakil

May 01, 2018


We will be going through this article with the assumption that you already know how to use Redux. If you don't, go ahead and learn to use it first. We have to know what Redux does to better understand how it does that.

Let's first create our reducer function.

const reducer = ( state = 0, action ) => {
switch(action.type){
case "INCREMENT":
return state + 1
case "DECREMENT":
return state - 1
default:
return state
}
}

Then we are going to create the createStore function which takes the reducer function as an argument and returns three methods. Namely, getState , dispatch , andsubscribe. We will also have the unsubscribe feature which is a function returned by the subscribe method.

const reducer = ( state = 0, action ) => {
switch(action.type){
case "INCREMENT":
return state + 1
case "DECREMENT":
return state - 1
default:
return state
}
}
// When createStore is called with a reducer function, it returns 4 methods.
// We will just create the empty methods now as a first.
function createStore(reducer){
function getState(){
}
function dispatch(){
}
function subscribe(){
// invoking this returned function will remove the subscriber function
return function(){
}
}
return {
getState: getState,
dispatch: dispatch,
subscribe: subscribe,
}
}

Now when you call store = createStore(reducer), all the methods will be available as store.getState(), store.dispatch() etc. They don't do anything for now, but that's a step forward. Let's move on.

We are going to get the methods working one-by-one now. First beginning with the getState method.

const reducer = ( state = 0, action ) => {
switch(action.type){
case "INCREMENT":
return state + 1
case "DECREMENT":
return state - 1
default:
return state
}
}
function createStore(reducer){
// declaring the variable that holds state of the store
var state
function getState(){
return state
}
function dispatch(){
}
function subscribe(){
// invoking this returned function will remove the subscriber function
return function(){
}
}
return {
getState: getState,
dispatch: dispatch,
subscribe: subscribe,
}
}

The state variable points to the state of our application. getState method just returns that variable. Pretty anti-climatic, huh? Invoking the getState method will return undefinednow. Because the state variable's value has not been set yet. Only the dispatch method will set values to the state variable. Next, we are going to write the dispatch method.

const reducer = ( state = 0, action ) => {
switch(action.type){
case "INCREMENT":
return state + 1
case "DECREMENT":
return state - 1
default:
return state
}
}
function createStore(reducer){
// declaring the variable that holds state of the store
var state
function getState(){
return state
}
function dispatch(action){
// passing the action and the state parameters to the reducer function. The returned value is set as state
state = reducer(state, action)
}
function subscribe(){
// invoking this returned function will remove the subscriber function
return function(){
}
}
return {
getState: getState,
dispatch: dispatch,
subscribe: subscribe
}
}
// testing the dispatch method
const store = createStore(reducer)
// this should set the state to 1
store.dispatch({ type: "INCREMENT" })
console.log(store.getState())
// this should set the state back to 0
store.dispatch({ type: "DECREMENT" })
console.log(store.getState())

The dispatch method takes an action object as the parameters. It then calls the reducer function with the current state and action arguments. Earlier, we provided the reducer function as an argument to the createStore function to create the store. reducer function is still available there to use because of JavaScript closure. We then set the reducer function's return value as the new state of our store. Now, if you call dispatch and then getState you will get the correct state of the store. Next, we are going to implement the subscribe method.

The subscribe method takes a function as an argument and runs that function every time the state is changed via dispatch.

const reducer = ( state = 0, action ) => {
switch(action.type){
case "INCREMENT":
return state + 1
case "DECREMENT":
return state - 1
default:
return state
}
}
function createStore(reducer){
var state
// this array contains all the subscriber functions.
var subscribers = []
function getState(){
return state
}
function dispatch(action){
// passing the action and the state parameters to the reducer function. The returned value is set as state
state = reducer(state, action)
// each time the dispatch function runs, we loop through all the functions stored in the subscribers array and invoke them.
subscribers.forEach( (subscriber) => {
subscriber()
} )
}
function subscribe(subscriber){
// functions passed to the subscribe method are added to the subscribers array
subscribers.push(subscriber)
// invoking this returned function will remove the subscriber function
return function(){
}
}
return {
getState: getState,
dispatch: dispatch,
subscribe: subscribe
}
}
// testing the subscriber methods
const store = createStore(reducer)
store.subscribe( () => {
// this function is going to be run each time dispatch is called
console.log("the current state is ", store.getState())
} )
store.dispatch({ type: "INCREMENT" })
store.dispatch({ type: "INCREMENT" })
store.dispatch({ type: "INCREMENT" })
store.dispatch({ type: "DECREMENT" })
store.dispatch({ type: "DECREMENT" })

It's the Observer Pattern in action. All the functions we pass to the subscribe method are stored in an array. Each time the dispatch method is called, we loop through all the functions stored in that array, in our case the subscribers array, and invoke all of them.

Now only the unsubscribe part left. The unsubscribe feature is returned as a function when we call the subscribe method. Invoking this function will remove that particular subscriber from the subscribers array. Let's knock that off!

const reducer = ( state = 0, action ) => {
switch(action.type){
case "INCREMENT":
return state + 1
case "DECREMENT":
return state - 1
default:
return state
}
}
function createStore(reducer){
var state
// this array contains all the subscriber functions.
var subscribers = []
function getState(){
return state
}
function dispatch(action){
// passing the action and the state parameters to the reducer function. The returned value is set as state
state = reducer(state, action)
// each time the dispatch function runs, we loop through all the functions stored in the subscribers array and invoke them.
subscribers.forEach( (subscriber) => {
subscriber()
} )
}
function subscribe(subscriber){
// functions passed to the subscribe method are added to the subscribers array
subscribers.push(subscriber)
// invoking this returned function will remove the subscriber function
return function(){
// getting the index of the subscriber in the subscribers array
let index = subscribers.indexOf(subscriber)
// deleting the array element using the index
subscribers.splice(index, 1)
}
}
return {
getState: getState,
dispatch: dispatch,
subscribe: subscribe
}
}
// testing the subscriber methods
const store = createStore(reducer)
const unsubscribe = store.subscribe( () => {
// this function is going to be run each time dispatch is called
console.log("the current state is ", store.getState())
} )
store.dispatch({ type: "INCREMENT" })
store.dispatch({ type: "INCREMENT" })
// calling unscribe will stop executing the subscriber function
unsubscribe()
store.dispatch({ type: "INCREMENT" })
store.dispatch({ type: "DECREMENT" })
store.dispatch({ type: "DECREMENT" })

And that's our Redux! Obviously it does not have all the bells and whistles the original Redux provides. We also did not include any error checking or whatsoever. But it's a good starting point to understand how Redux works under the hood. I highly encourage you to read through the source code of Redux as the next step https://github.com/reduxjs/redux/

Share article