Redux

The story about state

MV* (Angular, Backbone, Ember)

  • Model: manages the behavior and data of the application domain
  • View: represents the display of the model in the UI
  • Controller: takes user input, manipulates the model and causes the view to update

What's wrong?

UI

Filters

Search Results

MV* (Angular, Backbone, Ember)

Sorting

UI

UI

Tracking

Log

Validation

Locale

Tabs

Roundtrip

Earlier/later

Sort/filter business logic

Transit switch

What's wrong?

MV* (Angular, Backbone, Ember)

  • application state in multiple places
  • application state is not predictable
  • easy to break something by accident because of dependencies
  • hard to track bugs
  • hard to explain application logic to newcomers

Something happened

State changed

UI is rendered

UI is rendered

Something happened

State changed

UI is rendered

UNI-DIRECTIONAL

DATA FLOW

New mindset

Here comes Redux

Application state is:

ONE

SINGLE

JAVASCRIPT

OBJECT

READ-ONLY

Common Redux misconception: state is held in a “giant object”. It’s just object referencing a few other objects. Nothing giant about it.

Dan Abramov (creator of Redux)

How to change the state

CURRENT

STATE

PURE

FUNCTION*

ACTION

NEW STATE

* No side effects

Reducer

const newState = reducer(currentState, action);
// action
{ type: 'ACTION TYPE', /*...*/ }

YOU SHALL NOT MUTATE  THE STATE

TODO app example

// state of simple TODO app
const initialState = {
  visibilityFilter: 'all',
  todos: []
};

function todosReducer(state = initialState, action) {
    switch (action.type) {
      case ADD_TODO:
        const todoItem = {
          text: action.text, 
          completed: false 
        };
        return R.append(todoItem, state.todos);
      default:
        return state;
    }
}

Helper libs:

R.append('tests', ['write', 'more']);
//=> ['write', 'more', 'tests']
R.assoc('c', 3, {a: 1, b: 2});
//=> {a: 1, b: 2, c: 3}

Redux store

import { createStore } from 'redux';

const store = createStore(reducer, initialState);

Store

getState()

subscribe()

dispatch()

subscribe to state changes in UI

get the current state of the app

dispatch an action to reducers

Selectors

{
    products: [
        {
            id: 1,
            title: 'Product 1',
            description: 'Lorem ipsum',
            price: 1000
        },
        {
            id: 2,
            title: 'Product 2',
            description: 'Lorem ipsum',
            price: 2000
        }
    ],
    user: {
        name: 'John Doe',
        role: 'admin',
        currency: 'EUR'
    },
    basket: {
        items: [1, 1, 2],
        totalPrice: 4000
    }
}
        

Reselect

  • Selectors extracts parts of the store
  • Efficient
  • Composable
import { createSelector } from 'reselect'

const productsSelector = state => state.products;
const basketItemsSelector = state => state.basket.items;

const totalProductsSelector = createSelector(
  productsSelector,
  (items) => R.length(items)
)

const basketViewSelector = createSelector(
  productsSelector,
  basketItemsSelector,
  (products, basketItems) => {
     // produce data needed by the view
  }
)

Redux data flow

Action

Reducer

Store

View

<input ng-model="vm.text">
<a ng-click="vm.addTodo(vm.text)">
  add
</a>
<todo-item todo="todo"
    ng-repeat="todo in vm.todos">
</todo-item>
{
  type: 'ADD TODO',
  text: 'Learn Redux'
}


(state, action) => newState
{
  visibilityFilter: 'all',
  todos: ['Learn Redux']
}

 Learn Redux

add

add

Learn Redux

Why Redux?

  • leverages functional programming principles
  • single source of truth (store)
  • easy to reason about the application state
  • easy to test
  • you can use it with vanilla JS, Angular, React or whatever
  • very small footprint and API surface (228 Bytes min+gzip)

Time Traveling

+

Hot Module Reloading

Connecting UI to Redux

import ...
 
angular.module('app', ['ngRedux', 'app.todos'])
    .config(($ngReduxProvider) => {
        const rootReducer = combineReducers({todos, user});
        $ngReduxProvider.createStoreWith(rootReducer);
      });

angular.module('app.todos', ['ngRedux'])
    .controller('TodosCtrl', ($ngRedux, $scope) => {

        let unsubscribe = $ngRedux.connect(sliceOfTheState, availableActions)(this);
        $scope.$on('$destroy', unsubscribe);
      });

Redux bindings:

How to start with Redux

  1. Design your store
  2. List your actions
  3. Create reducers
  4. Connect UI to store
{
    products: [
        {
            id: 1,
            title: 'Product 1',
            description: 'Lorem ipsum',
            price: 1000
        },
        {
            id: 2,
            title: 'Product 2',
            description: 'Lorem ipsum',
            price: 2000
        }
    ],
    user: {
        name: 'John Doe',
        role: 'admin',
        currency: 'EUR'
    },
    basket: {
        items: [1, 1, 2],
        totalPrice: 4000
    }
}
function addItemToBasket(itemId) {
    return {
        type: 'ADD_ITEM_TO_BASKET',
        id: itemId
    }
}
// basket reducer
function basket(state = initialState, action) {
    switch (action.type) {
      case 'ADD_ITEM_TO_BASKET':
        return R.append(
            action.id,
            state.basket.items
        );
      ...
      default:
        return state;
    }
}
<div class="product" ng-repeat="product in vm.products">
    <product-details item="item"></product-details>
    <a ng-click="vm.addItemToBasket(product.id)">
        add to basket
    </a>
</div>

Questions?

Redux

By Damian Kowalski

Redux

The story about state

  • 1,062