Understanding Redux without React

October 01, 2021

redux-without-react

I always use to talk about react and redux in the same breath and thought redux does not exist without react. This was a big misconception that was cleared by responses to my tweet : image

In this post I will share how to learn redux in isolation and that will answer the question is Redux relevant today ?

What is Redux

Redux is predictable state container for JS Apps as per official docs, let's break down this definition :

  • Predictable : State changes to application is predictable, which can be tracked over time (time travel)
  • State Container: Redux stores the state of our application. App State means state represented by all the individual components of the application
  • JS Apps : Redux can be used with any UI library(React/Vue/Angular/Vanilla...), this was unclear to me

Why Redux

Let's see how complex state management can be without Redux

image Components manage their State internally, but when there is a need of communication of state between different components then we need to lift the state up to a common ancestor component and then drill it down to the component which needs it. In above ex: name state (+ setter method) is managed by Component C , then D requires it , we lift to A , but suppose Component F requires it then we need to move to App Component and then pass it down to Component F. This state management becomes messy and complex as the App grows.

You might think this is classical example of Props Drilling and can be solved by Context, then here is a great article on it.

Now lets figure out how Redux can simply above state management process : image

Redux maintains a centralized store which holds state of app, each component which have subscribed the store , receive the updated state. We will look into the complete flow of Redux in next section.

Real Life Example

All of us, would have visited a Bank at least once in our life for depositing / withdrawal/ etc... We don't go to bank's vault directly but ask to the cashier, where the bank staff handles operation for us. We give deposit/withdraw request by filling a slip and giving it cashier. Let's think this scenario in terms of Redux :

  • Bank's Vault is the store which stores all the money
  • Cashier is the Reducer which executes users action to update money in the vault
  • Customer dispatches an Action describing the intent

Principles of Redux

image

  • Store holds the state of application The state of whole application is stored in an object within a single store
  • Action describes the changes in the state of the application Cannot directly update the state object that is done by redux only
  • Reducer which carries out the actual state transition based upon the action. Pure reducers which take state and action and returns a new state.

Here is how we can implement above concepts in the banking scenario example : Action

// Actions Objects to withdraw and deposit money { type: 'WITHDRAW_MONEY', payload: 1000 } { type: 'DEPOSIT_MONEY', payload: 500 } // Action Creator function withdrawMoney() { return { type: "WITHDRAW_MONEY", payload: 1000, }; }

Reducer

const initialState = { amount: 5000, } const reducer = (state = initialState, action) => { switch (action.type) { case "WITHDRAW_MONEY": return { ...state, amount: state.amount - action.payload } case "DEPOSIT_MONEY": return { ...state, amount: state.amount + action.payload } default: return state } }

Store

const redux = require("redux") const createStore = redux.createStore const store = createStore(reducer) // access to State console.log("Initial State", store.getState()) //register listeners via subscribe(listener) const unsubscribe = store.subscribe(() => console.log("Update State :", store.getState()) ) //state update via dispatch(action) store.dispatch(withdrawMoney()) //handles unregistering of listeners by function returned by subscriber unsubscribe()

Going back to the Bank analogy, our bank is expanding and opening current accounts for businesses. It would be difficult to manage retail and business customers from single window as both types of customers have different types of need. So to manage all customers efficiently , bank opens a new window called 'Current Accounts' ( a new Reducer in Redux terms)

const initialState = { amount: 10000, } const currentAccountsReducer = (state = initialState, action) => { switch (action.type) { case "WITHDRAW_MONEY_CURRENT": return { ...state, amount: state.amount - action.payload } case "DEPOSIT_MONEY_CURRENT": return { ...state, amount: state.amount + action.payload } default: return state } }

Now we have to combine the two reducers to create the store (as it can be only one for entire application).In Bank's analogy this can be kind of token vending machine which gives the customer a token for saving / current account facilities.

const combineReducers = redux.combineReducers const createStore = redux.createStore const rootReducer = combineReducers({ savings: savingAccountsReducer, current: currentAccountsReducer, }) const store = createStore(combineReducers)

Whenever an action is dispatched, it goes to both the reducers but only one acts on it , other one ignores.

Middleware It is the how we can extend Redux with custom functionality. It gives us a point to hook our functionality after action is dispatched and before it reaches the reducer. One of commonly used middleware is redux logger

const reduxLogger = require("redux-logger") const logger = reduxLogger.createLogger() const applyMiddleware = redux.applyMiddleware const store = createStore(combineReducers, applyMiddleware(logger))

👇 Here is how we get the state transition in Redux image

Async Actions

Up till now we have seen synchronous actions ~ as soon as action is dispatched , reducers update the state immediately but in a real world scenario we have to make async API calls to fetch data from endpoint.

Let's see how to fetch data from API and store in a Redux store.

Firstly let's figure out the initial State of Application

const initialState = { loading: false, error: "", data: [], }

Since dispatch method needs to be called asynchronously so we would need a middleware called 'redux-thunk' which will handle the function returned from our action creator.

const store = createStore(rootReducer, applyMiddleware(thunk))

👇 CodeSandbox to practice above concepts :


In order to understand how to use Redux with React , you can read this post