Redux

Redux is a Predictable State Container for JS Apps.

Redux is based on Flux pattern which complements React’s composable view components by utilizing a unidirectional data flow. Flux applications have three major parts: the dispatcher, the stores, and the views (React components).

flux architecture

Unidirectional data flow

Redux is a simple implementation of this abstract concept-

Actions Reducers Stores

In this implementation a single, global, Store is delegated to contain all the application state.

The state can be changed dispatching Actions to the store.

Each action produces a new state (the state is never changed, a new state is produced and that is the new application state), through the usage of one or more reducers.

(Smart) Components can be connected to the store and be notified when the state changes, so that views are automatically updated.

redux basic animation

Basic sync flow of state evolution in redux.

Actions

In Redux, actions are actions descriptors, generated by an action creator. Actions descriptors are usually defined by an action type and a set of parameters that specify the action payload.

const CHANGE_TITLE= 'CHANGE_TITLE';

// action creator
function changeTitle(newTitle) {
    return {
        type: CHANGE_TITLE,
        title: newTitle
    };
}

Reducers

Reducers are functions that receive an action and the current state and:

  • produce a new state, for each recognized action
  • produce the current state for unrecognized actions
  • produce initial state, if the current state is undefined
function reducer(state = {title: "CHANGE_ME"}, action) {
    switch (action.type) {
        case CHANGE_TITLE:
            return {title: action.title};
        default:
            return state;
    }
}

Store

The redux store combines different reducers to produce a global state, with a slice for each used reducer.

var rootReducer = combineReducers({
   slice1: reducer1,
   slice2: reducer2
});
var initialState = {slice1: {}, slice2: {}};

var store = createStore(rootReducer, initialState);

The Redux store receives actions, through a dispatch method, and creates a new application state, using the configured reducers.

store.dispatch(changeTitle('New title'));

You can subscribe to the store, to be notified whenever the state changes.

store.subscribe(function handleChange() {});

Redux DevTools

We strongly recommend the installation of Redux DevTools which brings a lot of useful features

Debugger

It will contain the list of actions dispatched where you can see the action payload, the state diff and the final state

redux dev tools

Redux dev tools

Time travel

The DevTools allows you to do “time-travel debugging”, stepping back and forth in the action history to see the entire app state and UI at different points in time.

Live Editing

You can dispatch you actions using the dispatcher built-in without having to trigger it from an user interaction or programmatically

Redux Middleware

In these frameworks, a middleware is some code you can put between the framework receiving a request, and the framework generating a response The best feature of middleware is that it’s composable in a chain. We use multiple independent third-party middleware in a single project such as:

  • Redux thunk (going to be fully replaced by redux-observable)
  • Redux Observable
redux basic animation

Basic sync flow of state evolution in redux.

Redux-Thunk

This middleware allows to perform simple asynchronous flows by returning a function from the action creator (instead of an action object).

// action creator
function changeTitleAsync() {
    return (dispatch, getState) => {
        myAsyncPromise.then( (newTitle) => {
            dispatch({
                type: CHANGE_TITLE,
                title: newTitle
            };)
        });
    }
}

This middleware is there from the beginning of the MapStore history. During the years, some better middlewares have been developed for this purpose. We want to replace it in the future with redux-observable.

Redux Observable

This middleware provides support for side-effects in MapStore using rxjs. The core object of this middleware is the epic

function (action$: Observable<Action>, store: Store): Observable<Action>;

The epic is a function that simply gets as first parameter an Observable (stream) emitting the actions emitted by redux. It returns another Observable (stream) that emits actions that will be forwarded to redux too.

So there are 2 streams:

  • Actions in
  • Actions out

A simple epic example can be the following:

const pingEpic = action$ =>
  action$.filter(action => action.type === 'PING')
    .mapTo({ type: 'PONG' });

Every time a ‘PING’ action is emitted, the epic will emit also the ‘PONG’ action.

Main features of Redux

Single source of truth

The global state of your application is stored in an object tree within a single store. This makes it easy to create universal apps, as the state from your server can be serialized and hydrated into the client with no extra coding effort. A single state tree also makes it easier to debug or inspect an application; it also enables you to persist your app’s state in development, for a faster development cycle.

Read only immutable state

The only way to change the state is to emit an action, an object describing what happened.

This ensures that neither the views nor the network callbacks will ever write directly to the state. Instead, they express an intent to transform the state. Because all changes are centralized and happen one by one in a strict order, there are no subtle race conditions to watch out for. As actions are just plain objects, they can be logged, serialized, stored, and later replayed for debugging or testing purposes.

Changes are made with pure functions

To specify how the state tree is transformed by actions, you write pure reducers.

Reducers are just pure functions that take the previous state and an action, and return the next state. Remember to return new state objects, instead of mutating the previous state. You can start with a single reducer, and as your app grows, split it off into smaller reducers that manage specific parts of the state tree. Because reducers are just functions, you can control the order in which they are called, pass additional data, or even make reusable reducers for common tasks such as pagination.

References

Here some useful links about the arguments discussed: