Principles of Redux

Redux is a JavaScript state management library that could run in different environments ( client, server and native). Redux centralizes the state tree of the application in one place. All the data of the application revolves around the same strict unidirectional data flow. This makes the logic of the app more predictable and easier to understand.

Single Store

The entire state of our application is contained in one single object tree called the store (single source of truth). The store contains a few methods to glue the Redux flow.

Redux store methods

Creating a store

import { createStore } from "redux" import reducer from "./reducers/index" //combine reducers const store = createStore(reducer)

The createStore function holds the entire state tree of our app. We can only have one store. We split our application logic instead into multiple reducers and then we use combine reducers to create a single root reducer out of many.

In the above example, we import our combine reducers and pass them as an argument on the createStore(reducers).

createStore can receive two additional arguments: A preloadledState, used for optionally hydrate the state from the server (server-side-rendering). And an enhancer for any middleware or third party utility tools. createStore(reducers, [preloadedState], [enhancer]).


Actions

The only wat to change the state is to emit and action. Actions are plain JavaScript objects that must have a type property describing the change. The rest of the object structure is up to us.

const action = { type: "ADD_TODO", payload: "Call mom", }

We use Action creators to make our actions portable and easy to test. Action creators are functions that simply return the action.

function addTodo(payload) { return { type: "ADD_TODO", payload, } }

The only way to change the state tree is by using the store.dispatch(action) function.

store.dispatch(addTodo("Call mom"))

2. Reducers

Reducers are pure functions that manage how the app's state changes in response to actions. Reducers always receive two arguments in the same order: the previous state.

A common pattern in Reducers is to use a JS switch statement to identify the invoked action. If the action.type matches a reducer case match. The reducer does its operation and returns the next state. If there isn't a match case, the default case should return the previous state.

Never Mutate the State. We create a copy with Object.assign(), the Spread syntax or any other immutable pattern.

function todoApp(todos = [], action) { switch (action.type) { case "ADD_TODO": return [...todos, action.payload] default: return todos } }

Multiple Reducers

As our application grows, we'll have multiple reducers managing different parts of the state. These reducer functions might live in different files.

We make a single reducer with combineReducers.

./reducers/index.js

import { combineReducers } from "redux" import exampleReducerOne from "./exampleReducerOne" import exampleReducerTwo from "./exampleReducerTwo" export default combineReducers({ exampleReducerOne, exampleReducerTwo, })

The createStore function needs to know about our reducers to glue the Redux flow.

App.js

import { createStore } from "redux" import reducer from "./reducers/index" const store = createStore(reducer)

Vanilla Demo

Let's make a basic demo to view the Redux flow in action and understand how the store, actions and reducers interact.

import * as Redux from "redux" //simple reducer function todoApp(todos = [], action) { switch (action.type) { case "ADD_TODO": return [...todos, action.payload] case "DELETE_TODO": return todos.filter(todo => todo.id !== action.id) default: return todos } } //create store pass to reducer const store = Redux.createStore(todoApp) //Add todo action creator let previousId = 0 function addTodo(text) { //action return { type: "ADD_TODO", payload: { text, id: previousId++, }, } } //Delete todo action creator function deleteTodo(id) { //action return { type: "DELETE_TODO", id, } }

Let's dispatch a few addTodo actions to our store.

//dispatch actions to store store.dispatch(addTodo("Call mom")) store.dispatch(addTodo("Go to market")) store.dispatch(addTodo("Finish post"))

Our store now knows about our new state changes.

//get new state from store console.log(store.getState()) //store prints: ;[ { id: 0, text: "Call mom", }, { id: 1, text: "Go to market", }, { id: 2, text: "Finish post", }, ]

Let's dispatch a deleteTodo action to our store.

//dispatch actions to store store.dispatch(deleteTodo(2))

The store new state:

//get new state from store console.log(store.getState()) //store prints: ;[ { id: 0, text: "Call mom", }, { id: 1, text: "Go to market", }, ]

View sandbox:

Edit ivstudio/redux-vanilla