Manage application state with Redux

By Kwinten Pisman
@KwintenP

About me

Kwinten Pisman

 

Frontend architect

 

Blogger - https://blog.kwinten.com

 

@KwintenP

State management

3 Types of state

Data state

Application state

Inner state

Simple component tree without state management

Component tree with models

Problems

Connectivity between models

Everyone can update from anywhere

Changes in one place trigger changes somewhere else

 

 

Component tree with single "Model"

Problems

Everyone can still update the model from anywhere

How are changes from one place reflected elsewhere

 

==> NO CLEAR DATAFLOW!

Component tree with single "Model" and a dispatcher

Unidirectional dataflow

Model

Listens to state from the model

(read-only)

Components

Sends messages to the dispatcher to update the model

Dispatcher

Listens to messages being sent

Updates itself and notifies components

Redux

Predictable state container for javascript apps

 

Created by Dan Abramov

 

An architecture

 

Unidirectional dataflow

 

Brings structure to state

 

 

Flux

An Architecture, not a framework

 

Created at Facebook

 

Unidirectional data flow

 

Brings structure to your data

 

Easier to reason about

Unidirectional dataflow in Redux

Model

Subscribes to state from the model

Components

Sends messages to the dispatcher to update the model

Dispatcher

Listens to messages being sent

Updates itself and notifies components

Store

Sends ACTIONS to the dispatcher to update the model

Listens to ACTIONS being sent

Reducers

Updates itself via the reducers and notifies components

Redux Principles

Single source of truth
 

State is immutable
 

Changes are made through reducers which are pure functions

Redux(.js) and NGRX/Store

Both are Redux implementations


Frameworks


Redux for all javascript apps


NGRX/Store integrated with Angular 2


Code you write is interchangeable!

What do you need to do

Subscribes to state from the model

Components

Dispatcher

Store

Sends ACTIONS to the dispatcher to update the model

Listens to ACTIONS being sent

Reducers

Updates itself via the reducers and notify components

Actions

let action: Action = {
    type: "ADD_TWEET",
    payload: {
        tweet: { 
            id: 1,
            userName: "@KwintenP",
            content: "I'm giving a talk at 
                        #jsbe on #redux",
            starred: true
        }
    }
}

Reducers

function tweetsReducer(
        state: State = [], 
        action: Action): Tweet[] {
    switch (action.type) {
        case "ADD_TWEET":
            return [...state, action.payload.tweet];
        default:
            return state;
    }
}

Store

Store is the single source of truth

 

Holds all the state in one object

 

Subscribe to it to get state updates

 

Store calls reducers with an action to create a new state object

Async actions

Initiate XHR request

Store

Dispatcher

SET_BUSY_FLAG

Receive result from XHR request

REMOVE_BUSY_FLAG

Handle result

ADD_DATA

{
    tweets: [],
    busyFlag: true
}
{
    tweets: [],
    busyFlag: false
}
{
    tweets: [
        {
            id: 1, 
            username: "@KwintenP", 
            content: "I'm giving 
                a talk at JSBE", 
            starred: true
        }    
    ],
    busyFlag: false
}

State design

Important to do up front

 

Divide it between ui and data state

 

As you would design a database

 

No double entries - Normalise if needed

 

Not everything should be put into the store!

 

Be pragmatic!

What (not) to put in your store

Do Don't
Ui Component state for rerender
 
Everything else
Data Shared State
State to cache
Rehydration state
Everything else

Example state design

const stateExample = {
    ui: {
        mainPage (smart component): {
            sidebarCollapsed: true,
            topbarCollapsed: true
        }
    },
    data: {
        tweets: [....],
        users: [....]
    }
}

State immutability

Root

ui

data

mainPage

tweets

users

Reducer composition

const stateExample = {
    ui: { -> uiReducer
        mainPage (smart component): { -> mainPageReducer
            sidebarCollapsed: true,
            topbarCollapsed: true
        }
    },
    data: { -> dataReducer
        tweets: [....], -> tweetsReducer
        users: [....] -> usersReducer
    }
}

DataReducer

export function dataReducer(state: DataState = initialTweetState, action: Action): DataState {
    switch (action.type) {
        case ADD_TWEET:
        case REMOVE_TWEET:
        case SET_TWEETS:
        case UPDATE_TWEET:
        case TOGGLE_STAR_TWEET:
            return Object.assign(
                    {}, 
                    state, 
                    {tweets: tweetsReducer(state.tweets, action)}
            );
        case ADD_USER:
        case REMOVE_USER:
            return Object.assign(
                    {}, 
                    state, 
                    {users: usersReducer(state.tweets, action)}
            );
        default:
            return state;
    }
}

DataReducer usage

//BEFORE:
let beforeState: DataState = {
    users: [],
    tweets: [ 
        {id: 1, ... }
    ]
}

dataReducer(beforeState, {type: "ADD_USER", payload: {user: {id: 42, ...}}});

//AFTER
let afterState: DataState = {
    users: [
        {id: 42, ...}
    ],
    tweets: [ //--> SAME REFERENCE!
        {id: 1, ... }
    ]
}

Combine reducers

export const rootReducer = {
    ui: uiReducer,
    data: dataReducer
}

new Store(combineReducers(rootReducer));

// pseudo code
uiReducer(state.ui, action);
dataReducer(state.data, action);

Redux middleware

Middleware

Middleware

Reducer

State

Action

New

State

Logging

Performance metrics

Authentication

Other advantages of Redux

Server side rendering

 

Devtools - Time travelling

 

Separate development view layer - state layer

 

Reuse of the state layer on the server side

 

Dumps of state for bugs

 

Ideal for hot module reloading

Redux summary

Single store with single state object

 

State MUST be immutable

 

Updates to the store are done by dispatching actions

 

The store is updated via Reducers

Questions?

Redux-thunk?

Redux selectors

Action creators

search(query: string): Observable<WineComSearchResult> {
  return this.http
      .get(`${WINE_COM_API_URL}catalog?apikey=${WINE_COM_API_KEY}&search=${query}`)
      .map((resp: Response) => JSON.parse(JSON.stringify(resp.json()), camelCaseReviver));
}
Made with Slides.com