React Redux for Beginners(part_2)

Sanje Qi
7 min readSep 22, 2019

--

part1

BASICS IN REDUX WITHOUT REACT

On the official Redux website it says: “Redux is a predictable state container for JavaScript apps.”. It can be used standalone or in connection with libraries, like React and Angular, to manage state in JavaScript applications.

Redux adopted a handful of constraints from the Flux architecture but not all of them. It has Actions that encapsulate information for the actual state update. It has a Store to save the state, too. However, the Store is a singleton. Thus, there are not multiple Stores like there used to be in the Flux architecture. In addition, there is no single Dispatcher. Instead, Redux uses multiple Reducers. Basically, Reducers pick up the information from Actions and “reduce” the information to a new state, along with the old state, that is stored in the Store. When state in the Store is changed, the View can act on this by subscribing to the Store.

View -> Action -> Reducer(s) -> Store -> View

So why is it called Redux? Because it combines the two words Reducer and Flux. The abstract picture of Redux should be imaginable now. The state doesn’t live in the View anymore, it is only connected to the View. What does connected mean? It is connected on two ends, because it is part of the unidirectional data flow. One end is responsible to trigger an Action to which updates the state eventually and the second end is responsible to receive the state from the Store. Therefore, the View can update accordingly to state changes but can also trigger state changes. The View, in this case, would be React, but Redux can be used with any other library or standalone as well. After all, it is only a state management container.

Action(s)

An action in Redux is a JavaScript object. It has a type and an optional payload. The type is often referred to as action type. While the type is a string literal, the payload can be anything from a string to an object.

In the beginning your playground to get to know Redux will be a Todo application. For instance, the following action in this application can be used to add a new todo item:

{type: 'TODO_ADD',todo: { id: '0', name: 'learn redux', completed: false },}

Executing an action is called dispatching in Redux. You can dispatch an action to alter the state in the Redux store. You only dispatch an action when you want to change the state. The dispatching of an action can be triggered in your View. It could be as simple as a click on a HTML button. In addition, the payload in a Redux action is not mandatory. You can define actions that have only an action type. In the end, once an action is dispatched, it will go through all reducers in Redux.

Reducer(s)

A reducer is the next part in the chain of the unidirectional data flow. The view dispatches an action, an action object with action type and optional payload, which passes through all reducers. What’s a reducer? A reducer is a pure function. It always produces the same output when the input stays the same. It has no side-effects, thus it is only an input/output operation. A reducer has two inputs: state and action. The state is always the global state object from the Redux store. The action is the dispatched action with a type and optional payload. The reducer reduces — that explains the naming — the previous state and incoming action to a new state.

(prevState, action) => newState

Apart from the functional programming principle, namely that a reducer is a pure function without side-effects, it also embraces immutable data structures. It always returns a newState object without mutating the incoming prevState object. Thus, the following reducer, where the state of the Todo application is a list of todos, is not an allowed reducer function:

function(state, action) {state.push(action.todo);return state;}

The Array push method mutates the previous state instead of returning a new state object. The following is allowed because it keeps the previous state intact and also return the new state:

function reducer(state, action) {return state.concat(action.todo);}

By using the JavaScript built-in concat functionality, the state and thus the list of todos is concatenated to another item. The other item is the newly added todo from the action. You might wonder: Does it embrace immutability now? Yes it does, because concat always returns a new array without mutating the old array. The data structure stays immutable.

But what about the action type? Right now, only the payload is used to produce a new state but the action type is ignored. So what can you do about the action type? Basically when an action object arrives at the reducers, the action type can be evaluated. Only when a reducer cares about the action type, it will produce a new state. Otherwise, it simply returns the previous state. In JavaScript, a switch case can help to evaluate different action types. Otherwise, it returns the previous state as default.

Imagine your Todo application would have a second action and action type that toggles a Todo to either completed or incomplete. The only information which is needed as payload is an identifier to identify the Todo in the state.

{type: 'TODO_TOGGLE',todo: { id: '0' },}

The reducer(s) would have to act on two actions now: TODO_ADD and TODO_TOGGLE. By using a switch case statement, you can branch into different cases. If there is not such a case, you return the unchanged state by default.

function reducer(state, action) {switch(action.type) {case 'TODO_ADD' : {// do something and return new state}case 'TODO_TOGGLE' : {// do something and return new state}default : return state;}}

The tutorial already discussed the TODO_ADD action type and its functionality. It simply concats a new todo item to the previous list of todo items. But what about the TODO_TOGGLE functionality?

function reducer(state, action) {switch(action.type) {case 'TODO_ADD' : {return state.concat(action.todo);}case 'TODO_TOGGLE' : {return state.map(todo =>todo.id === action.todo.id? Object.assign({}, todo, { completed: !todo.completed }): todo);}default : return state;}}

In the example, the built-in JavaScript functionality map is used to map over the state, the list of todos, to either return the intact todo or return the toggled todo. The toggled todo is identified by its id property. The JavaScript built-in functionality map always returns a new array. It doesn't mutate the previous state and thus the state of todos stays immutable and can be returned as a new state.

But isn’t the toggled todo mutated? No, because Object.assign() returns a new object without mutating the old object. Object.assign() merges all given objects from the former to the latter into each other. If a former object shares the same property as a latter object, the property of the latter object will be used. Thus, the completed property of the updated todo item will be the negated state of the old todo item.

Note that these functionalities, actions and reducer, are plain JavaScript. There is no function from the Redux library involved so far. There is no hidden library magic. It is only JavaScript with functional programming principles in mind.

There is one useful thing to know about the current reducer: It has grown in size that makes it less maintainable. In order to keep reducers tidy, the different switch case branches can be extracted as pure functions:

function reducer(state, action) {switch(action.type) {case 'TODO_ADD' : {return applyAddTodo(state, action);}case 'TODO_TOGGLE' : {return applyToggleTodo(state, action);}default : return state;}}function applyAddTodo(state, action) {return state.concat(action.todo);}function applyToggleTodo(state, action) {return state.map(todo =>todo.id === action.todo.id? Object.assign({}, todo, { completed: !todo.completed }): todo);}

In the end, the Todo application has two actions and one reducer by now. One last part in the Redux setup is missing: the Store.

Redux Store

So far, the Todo application has a way to trigger state updates (action(s)) and a way to reduce the previous state and action to a new state (reducer(s)). But no one is responsible to glue these parts together.

  • Where do I trigger actions?
  • Who delegates the actions to the reducer?
  • Where do I get the updated state to glue it to my View?

It is the Redux store. The store holds one global state object. There are no multiple stores and no multiple states. The store is only one instance in your application. In addition, it is the first library dependency you encounter when using Redux. Therefore, use the import statement to get the functionality to create the store object from the Redux library (after you have installed it with npm install --save redux).

import { createStore } from 'redux';

Now you can use it to create a store singleton instance. The createStore function takes one mandatory argument: a reducer. You already defined a reducer in the sections before which adds and completes todo items.

const store = createStore(reducer);

In addition, the createStore takes a second optional argument: the initial state. In the case of the Todo application, the reducer operates on a list of todos as state. The list of todo items should be initialized as an empty array or pre-filled array with todos. If it wasn't initialized, the reducer would fail because it would operate on an undefined argument.

const store = createStore(reducer, []);

Later in this tutorial, when using React with Redux, you will see another way to initialize state in Redux. Then you will use the reducer instead of the store to initialize state on a more fine-grained level.

Now you have a store instance that knows about the reducer. The Redux setup is done. However, the essential part is missing: You want to interact with the store. You want to dispatch actions to alter the state, get the state from the store and listen to updates of the state in the store.

So first, how to dispatch an action?

store.dispatch({type: 'TODO_ADD',todo: { id: '0', name: 'learn redux', completed: false },});

Second: How to get the global state from the store?

store.getState();

And third, how to subscribe (and unsubscribe) to the store in order to listen (and unlisten) for updates?

const unsubscribe = store.subscribe(() => {console.log(store.getState());});unsubscribe();

That’s all to it. The Redux store has only a slim API to access the state, update it and listen for updates. It is one of the essential constraints which made Redux so successful. to be continued :)

Source: StackOverflow, Robin Wieruch, Medium, ReactJs, MDN

--

--