Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Framework-Agnostic Web Applications with Redux at Frontend Conf 2017

Framework-Agnostic Web Applications with Redux at Frontend Conf 2017

The web is changing fast. If you're building web products today, it can be overwhelming. Which framework should your team adopt? What if something better suited comes out in a few months?

In this talk, we’ll explore Redux – an architecture that helps building applications that behave consistently and run across environments. Redux is gaining popularity within today’s most popular framework’s communities, by allowing developers to express their application logic in pure, easily testable, and reusable functions, enabling teams to keep shipping their products without rewriting their frontend every 6 months.

We'll use Redux to build a small web application and learn how to make use of the same codebase together with today's popular web frameworks – React, Angular, Vue – without changes.

João Figueiredo

September 01, 2017
Tweet

More Decks by João Figueiredo

Other Decks in Programming

Transcript

  1. Framework-Agnostic
    Web Applications

    View Slide

  2. Agnostic
    (adjective)

    View Slide

  3. Agnostic
    a: unwilling to commit to an opinion
    about something.
    (adjective)

    View Slide

  4. Agnostic
    a: unwilling to commit to an opinion
    about something.
    b: compatible with many types of
    platforms or operating systems.
    (adjective)

    View Slide

  5. Framework-Agnostic Web Apps

    View Slide

  6. Framework-Agnostic Web Apps
    Applications that are designed in such
    a way that make them not tied to a
    particular framework.

    View Slide

  7. Why is this important?

    View Slide

  8. let’s talk about
    THE WEB.

    View Slide

  9. 05 06 07 08 09 10 11 12 13 14 15
    ®
    React Native
    10 Years in Web

    View Slide

  10. 05 06 07 08 09 10 11 12 13 14 15
    ®
    React Native
    DOM

    View Slide

  11. 05 06 07 08 09 10 11 12 13 14 15
    ®
    React Native
    CSS

    View Slide

  12. 05 06 07 08 09 10 11 12 13 14 15
    ®
    React Native
    Applications

    View Slide

  13. 05 06 07 08 09 10 11 12 13 14 15
    ®
    React Native
    Tests

    View Slide

  14. 05 06 07 08 09 10 11 12 13 14 15
    ®
    React Native
    Dependencies

    View Slide

  15. 05 06 07 08 09 10 11 12 13 14 15
    ®
    React Native
    Tooling

    View Slide

  16. 05 06 07 08 09 10 11 12 13 14 15
    ®
    React Native
    Transpilation

    View Slide

  17. 05 06 07 08 09 10 11 12 13 14 15
    ®
    React Native

    View Slide

  18. Javascript
    FATIGUE

    View Slide

  19. View Slide

  20. View Slide

  21. View Slide

  22. View Slide

  23. View Slide

  24. View Slide

  25. 11 12 13 14 15 16 17
    npm modules over time
    [Breaking News]
    This is not going to slow down.

    View Slide

  26. This is not necessarily bad.

    View Slide

  27. This is not necessarily bad. We just have to get used to it.

    View Slide

  28. This is not necessarily bad. We just have to get used to it.
    Code that embraces change.

    View Slide

  29. This is not necessarily bad. We just have to get used to it.
    Code that embraces change.
    Agnostic Code.

    View Slide

  30. Framework-Agnostic Web Apps

    View Slide

  31. Framework-Agnostic Web Apps
    Allow us to easily switch to a better fitting
    framework without the need for a rewrite.

    View Slide

  32. Why is this important?

    View Slide

  33. the web moves faster than you
    Why is this important?
    (and that’s ok)

    View Slide

  34. How developers judge frameworks?

    View Slide

  35. How developers judge frameworks?

    View Slide

  36. How everybody-else judges
    frameworks?

    View Slide


  37. How everybody-else judges
    frameworks?
    *shrinks*

    View Slide

  38. Users don’t give a
    DAMN.

    View Slide

  39. 2.7B £
    ANGULAR 1

    View Slide

  40. VUE
    $6B

    View Slide

  41. REACT
    $8.8B

    View Slide

  42. Focus more on how to
    build great products.

    View Slide

  43. Views
    how it looks

    View Slide

  44. Views
    how it looks Routing
    what pages do I have

    View Slide

  45. Views
    how it looks
    App Logic
    what happens if I click here
    Routing
    what pages do I have

    View Slide

  46. Views
    how it looks
    App Logic
    what happens if I click here
    Routing
    what pages do I have
    Persistence
    how do I create/update data,
    optimistic updates, data consistency

    View Slide

  47. Views
    how it looks
    App Logic
    what happens if I click here
    Business Logic
    what data do I have, how do I interact with it it,
    who can do what, etc.
    Routing
    what pages do I have
    Persistence
    how do I create/update data,
    optimistic updates, data consistency

    View Slide

  48. the web moves faster than you
    Why is this important?
    (and that’s ok)

    View Slide

  49. the web moves faster than you
    Why is this important?
    (and that’s ok)
    users judge you on your product.
    (and that’s difficult on its own)

    View Slide

  50. How do I even FAWA?
    Nuff said.
    shamefully trying to create an acronym.

    View Slide

  51. REDUX
    *increasingly loud drum roll*

    View Slide

  52. A library to manage state of your
    application.
    — Redux docs

    View Slide

  53. A library to manage state of your
    application.
    — Redux docs
    Your framework-agnostic best friend.
    — Me

    View Slide

  54. REDUX APPS
    run in different environments
    (Good because the web moves fast)

    View Slide

  55. REDUX APPS
    run in different environments
    (Good because the web moves fast)
    (Good because making good products is hard)
    behave consistently and are easy to test.

    View Slide

  56. Store
    Action
    Reducer
    Component

    View Slide

  57. Store
    Action
    Reducer
    Component
    Framework-Agnostic
    Your framework
    Probably replaced every 6 months
    Build to last

    View Slide

  58. STORE ACTIONS REDUCERS

    View Slide

  59. import { createStore } from 'redux';
    const store = createStore();
    Redux Store

    View Slide

  60. store.getState();
    Holds the whole state tree of your application.
    Redux Store
    {
    todos: [
    { name: 'Attend Frontend Conf' },
    { name: 'Introduce Redux Store' },
    ],
    }

    View Slide

  61. Everything is state.

    View Slide

  62. Everything is state.
    Dissecting Twitter's Redux Store by Ryan Johnson

    View Slide

  63. Everything is state.
    Dissecting Twitter's Redux Store by Ryan Johnson
    Tweets
    Users
    Lists
    Cards
    Timeline
    Settings
    Notifications

    View Slide

  64. store.subscribe(listener);
    Handles state change listeners.
    Redux Store

    View Slide

  65. store.dispatch(action);
    Allows state to be updated by dispatching actions
    Redux Store

    View Slide

  66. STORE ACTIONS REDUCERS

    View Slide

  67. Redux Actions
    Payloads of information that send data from your application to
    the store.
    const action = {
    type: "ADD_TODO",
    payload: { name: "A new todo" }
    };
    store.dispatch(action);

    View Slide

  68. Redux Action Creators
    function renameTodo(index, name) {
    }
    return {
    type: "RENAME_TODO",
    payload: {
    index,
    name
    }
    };

    View Slide

  69. Redux Action Creators
    function renameTodo(index, name) {
    }
    return {
    type: "RENAME_TODO",
    payload: {
    index,
    name
    }
    };
    ACTION CREATOR

    View Slide

  70. Redux Action Creators
    function renameTodo(index, name) {
    }
    return {
    type: "RENAME_TODO",
    payload: {
    index,
    name
    }
    };
    ACTION

    View Slide

  71. Redux Action Creators
    store.dispatch(
    renameTodo(0, 'Naming todos is hard.’)
    );

    View Slide

  72. STORE ACTIONS REDUCERS
    ✅ ✅

    View Slide

  73. Redux Reducers
    Actions describe that something happened.
    Reducers specify how the state changes in response.
    (previous, action) => newState

    View Slide

  74. Redux Reducers
    function reducer(state = INITIAL_STATE, { type, payload }) {
    if (type === 'INCREMENT') {
    return state + 1;
    }
    if (type === 'INCREMENT_BY') {
    return state + payload;
    }
    return state;
    }
    ACTION

    View Slide

  75. Redux Reducers
    counterReducer(0, {
    type: ‘INCREMENT'
    });
    // 1

    View Slide

  76. Redux Reducers
    counterReducer(0, {
    type: ‘INCREMENT'
    });
    // 1
    counterReducer(5, {
    type: ‘INCREMENT_BY',
    payload: 10
    });
    // 15

    View Slide

  77. STORE ACTIONS REDUCERS
    ✅ ✅ ✅

    View Slide

  78. Write a simple redux application and use in React, Vue and
    Angular 2 without code changes.
    CHALLENGE #1

    View Slide

  79. Store
    Action
    Reducer
    Component
    Redux
    Your framework

    View Slide

  80. ADD
    Buy Groceries
    Go to Gym
    x

    View Slide

  81. ADD
    Buy Groceries
    Go to Gym
    x
    Step 1: design state

    View Slide

  82. ADD
    Buy Groceries
    Go to Gym
    x
    [
    { name: 'Buy Groceries', isCompleted: false },
    { name: 'Go To Gym', isCompleted: true }
    ]
    Step 1: design state

    View Slide

  83. Step 2: actions and action creators

    View Slide

  84. // actions/index.js
    export const ADD_TODO = 'ADD_TODO';
    export const TOGGLE_TODO = 'TOGGLE_TODO';
    export const addTodo = (name) => ({
    type: ADD_TODO,
    payload: name
    })
    export const toggleTodo = (index) => ({
    type: TOGGLE_TODO,
    payload: index
    });
    Step 2: actions and action creators

    View Slide

  85. Step 3: reducer

    View Slide

  86. // reducers/index.js
    export function todos(state = [], { type, payload }) {
    if (type === ADD_TODO) {
    return state.concat([ { name: payload, isCompleted: false } ]);
    }
    if (type === TOGGLE_TODO) {
    return [
    ...state.slice(0, payload - 1),
    {
    ...state[payload],
    isCompleted: !state[payload].isCompleted
    },
    ...state.slice(payload + 1)
    ];
    }
    return state;
    }
    Step 3: reducer

    View Slide

  87. ADD

    View Slide

  88. ADD
    Step 4: create store

    View Slide

  89. import reducer from './reducers';
    const store = createStore(reducer);
    store.subscribe(() => {
    const todos = store.getState()
    // []
    render(todos);
    });
    ADD
    Step 4: create store

    View Slide

  90. import reducer from './reducers';
    const store = createStore(reducer);
    store.subscribe(() => {
    const todos = store.getState()
    // []
    render(todos);
    });
    ADD
    Step 4: create store
    render(todos);

    View Slide

  91. ADD
    Buy Groceries

    View Slide

  92. ADD
    Buy Groceries
    store.dispatch(
    addTodo('Buy groceries’)
    );

    View Slide

  93. ADD
    Buy Groceries
    store.dispatch(
    addTodo('Buy groceries’)
    );
    store.dispatch(
    );

    View Slide

  94. ADD
    Buy Groceries
    store.subscribe(() => {
    const todos = store.getState()
    // [
    // { name: 'Buy groceries', isCompleted: false }
    // ]
    render(todos);
    });

    View Slide

  95. Go to Gym ADD
    Buy Groceries store.dispatch(
    addTodo('Go to gym’)
    );

    View Slide

  96. ADD
    Buy Groceries
    Go to Gym
    store.subscribe(() => {
    const todos = store.getState()
    // [
    // { name: 'Buy groceries', isCompleted: false }
    // { name: 'Go to Gym', isCompleted: false }
    // ]
    render(todos);
    });

    View Slide

  97. ADD
    Buy Groceries
    Go to Gym
    x
    store.dispatch(
    toggleTodo(1)
    );

    View Slide

  98. The missing bits
    const render = state => ‘html-ish’;
    el.addEventListener('event', store.dispatch)

    View Slide

  99. Let’s integrate it with

    View Slide

  100. View Slide

  101. import React from 'react'
    import { render } from 'react-dom'
    import { Provider } from 'react-redux'
    import { createStore } from 'redux'
    import reducer from './reducers';
    import TodoApp from ‘./components/TodoApp';
    const store = createStore(reducer);
    render(


    ,
    document.getElementById('root')
    )

    View Slide

  102. import React from 'react'
    import { render } from 'react-dom'
    import { Provider } from 'react-redux'
    import { createStore } from 'redux'
    import reducer from './reducers';
    import TodoApp from ‘./components/TodoApp';
    const store = createStore(reducer);
    render(


    ,
    document.getElementById('root')
    )
    import { BrowserModule }
    from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import reducer from ‘./reducers';
    import TodoApp from ‘./components/TodoApp';
    @NgModule({
    imports: [
    BrowserModule,
    StoreModule.provideStore(reducer)
    ],
    bootstrap: [TodoApp]
    })
    export class AppModule { }

    View Slide

  103. import React from 'react'
    import { render } from 'react-dom'
    import { Provider } from 'react-redux'
    import { createStore } from 'redux'
    import reducer from './reducers';
    import TodoApp from ‘./components/TodoApp';
    const store = createStore(reducer);
    render(


    ,
    document.getElementById('root')
    )
    import { BrowserModule }
    from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import reducer from ‘./reducers';
    import TodoApp from ‘./components/TodoApp';
    @NgModule({
    imports: [
    BrowserModule,
    StoreModule.provideStore(reducer)
    ],
    bootstrap: [TodoApp]
    })
    export class AppModule { }
    import Vue from 'vue';
    import { createStore } from 'redux';
    import reducer from './reducers';
    import TodoApp from './components/TodoApp';
    const store = createStore(reducer);
    Vue.use({
    install: () =>
    Object.defineProperty(
    Vue.proprotype,
    '$store', {
    get() {
    return store;
    },
    }),
    });
    new Vue({
    el: '#root',
    render: h => h(TodoApp),
    });

    View Slide

  104. import React from 'react'
    import { render } from 'react-dom'
    import { Provider } from 'react-redux'
    import { createStore } from 'redux'
    import reducer from './reducers';
    import TodoApp from ‘./components/TodoApp';
    const store = createStore(reducer);
    render(


    ,
    document.getElementById('root')
    )
    import { BrowserModule }
    from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import reducer from ‘./reducers';
    import TodoApp from ‘./components/TodoApp';
    @NgModule({
    imports: [
    BrowserModule,
    StoreModule.provideStore(reducer)
    ],
    bootstrap: [TodoApp]
    })
    export class AppModule { }
    import Vue from 'vue';
    import { createStore } from 'redux';
    import reducer from './reducers';
    import TodoApp from './components/TodoApp';
    const store = createStore(reducer);
    Vue.use({
    install: () =>
    Object.defineProperty(
    Vue.proprotype,
    '$store', {
    get() {
    return store;
    },
    }),
    });
    new Vue({
    el: '#root',
    render: h => h(TodoApp),
    });
    Bootstrapping Framework

    View Slide

  105. import React from 'react'
    import { render } from 'react-dom'
    import { Provider } from 'react-redux'
    import { createStore } from 'redux'
    import reducer from './reducers';
    import TodoApp from ‘./components/TodoApp';
    const store = createStore(reducer);
    render(


    ,
    document.getElementById('root')
    )
    import { BrowserModule }
    from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import reducer from ‘./reducers';
    import TodoApp from ‘./components/TodoApp';
    @NgModule({
    imports: [
    BrowserModule,
    StoreModule.provideStore(reducer)
    ],
    bootstrap: [TodoApp]
    })
    export class AppModule { }
    import Vue from 'vue';
    import { createStore } from 'redux';
    import reducer from './reducers';
    import TodoApp from './components/TodoApp';
    const store = createStore(reducer);
    Vue.use({
    install: () =>
    Object.defineProperty(
    Vue.proprotype,
    '$store', {
    get() {
    return store;
    },
    }),
    });
    new Vue({
    el: '#root',
    render: h => h(TodoApp),
    });
    Bootstrapping Framework
    import React from 'react'
    import { render } from 'react-dom'
    render(
    document.getElementById('root')
    )

    View Slide

  106. import React from 'react'
    import { render } from 'react-dom'
    import { Provider } from 'react-redux'
    import { createStore } from 'redux'
    import reducer from './reducers';
    import TodoApp from ‘./components/TodoApp';
    const store = createStore(reducer);
    render(


    ,
    document.getElementById('root')
    )
    import { BrowserModule }
    from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import reducer from ‘./reducers';
    import TodoApp from ‘./components/TodoApp';
    @NgModule({
    imports: [
    BrowserModule,
    StoreModule.provideStore(reducer)
    ],
    bootstrap: [TodoApp]
    })
    export class AppModule { }
    import Vue from 'vue';
    import { createStore } from 'redux';
    import reducer from './reducers';
    import TodoApp from './components/TodoApp';
    const store = createStore(reducer);
    Vue.use({
    install: () =>
    Object.defineProperty(
    Vue.proprotype,
    '$store', {
    get() {
    return store;
    },
    }),
    });
    new Vue({
    el: '#root',
    render: h => h(TodoApp),
    });
    Bootstrapping Framework
    import React from 'react'
    import { render } from 'react-dom'
    render(
    document.getElementById('root')
    )
    import { BrowserModule }
    from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    @NgModule({
    imports: [
    BrowserModule,
    ],
    bootstrap: [ ]
    })
    export class AppModule { }

    View Slide

  107. import React from 'react'
    import { render } from 'react-dom'
    import { Provider } from 'react-redux'
    import { createStore } from 'redux'
    import reducer from './reducers';
    import TodoApp from ‘./components/TodoApp';
    const store = createStore(reducer);
    render(


    ,
    document.getElementById('root')
    )
    import { BrowserModule }
    from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import reducer from ‘./reducers';
    import TodoApp from ‘./components/TodoApp';
    @NgModule({
    imports: [
    BrowserModule,
    StoreModule.provideStore(reducer)
    ],
    bootstrap: [TodoApp]
    })
    export class AppModule { }
    import Vue from 'vue';
    import { createStore } from 'redux';
    import reducer from './reducers';
    import TodoApp from './components/TodoApp';
    const store = createStore(reducer);
    Vue.use({
    install: () =>
    Object.defineProperty(
    Vue.proprotype,
    '$store', {
    get() {
    return store;
    },
    }),
    });
    new Vue({
    el: '#root',
    render: h => h(TodoApp),
    });
    Bootstrapping Framework
    import React from 'react'
    import { render } from 'react-dom'
    render(
    document.getElementById('root')
    )
    import { BrowserModule }
    from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    @NgModule({
    imports: [
    BrowserModule,
    ],
    bootstrap: [ ]
    })
    export class AppModule { }
    import Vue from 'vue';
    new Vue({
    el: '#root',
    render: h => h( ),
    });

    View Slide

  108. import React from 'react'
    import { render } from 'react-dom'
    import { Provider } from 'react-redux'
    import { createStore } from 'redux'
    import reducer from './reducers';
    import TodoApp from ‘./components/TodoApp';
    const store = createStore(reducer);
    render(


    ,
    document.getElementById('root')
    )
    import { BrowserModule }
    from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import reducer from ‘./reducers';
    import TodoApp from ‘./components/TodoApp';
    @NgModule({
    imports: [
    BrowserModule,
    StoreModule.provideStore(reducer)
    ],
    bootstrap: [TodoApp]
    })
    export class AppModule { }
    import Vue from 'vue';
    import { createStore } from 'redux';
    import reducer from './reducers';
    import TodoApp from './components/TodoApp';
    const store = createStore(reducer);
    Vue.use({
    install: () =>
    Object.defineProperty(
    Vue.proprotype,
    '$store', {
    get() {
    return store;
    },
    }),
    });
    new Vue({
    el: '#root',
    render: h => h(TodoApp),
    });
    import React from 'react'
    import { render } from 'react-dom'
    render(
    document.getElementById('root')
    )
    import { BrowserModule }
    from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    @NgModule({
    imports: [
    BrowserModule,
    ],
    bootstrap: [ ]
    })
    export class AppModule { }
    import Vue from 'vue';
    new Vue({
    el: '#root',
    render: h => h( ),
    });

    Bootstrapping Framework

    View Slide

  109. import React from 'react'
    import { render } from 'react-dom'
    import { Provider } from 'react-redux'
    import { createStore } from 'redux'
    import reducer from './reducers';
    import TodoApp from ‘./components/TodoApp';
    const store = createStore(reducer);
    render(


    ,
    document.getElementById('root')
    )
    import { BrowserModule }
    from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import reducer from ‘./reducers';
    import TodoApp from ‘./components/TodoApp';
    @NgModule({
    imports: [
    BrowserModule,
    StoreModule.provideStore(reducer)
    ],
    bootstrap: [TodoApp]
    })
    export class AppModule { }
    import Vue from 'vue';
    import { createStore } from 'redux';
    import reducer from './reducers';
    import TodoApp from './components/TodoApp';
    const store = createStore(reducer);
    Vue.use({
    install: () =>
    Object.defineProperty(
    Vue.proprotype,
    '$store', {
    get() {
    return store;
    },
    }),
    });
    new Vue({
    el: '#root',
    render: h => h(TodoApp),
    });
    Bootstrap Redux Part

    View Slide

  110. import React from 'react'
    import { render } from 'react-dom'
    import { Provider } from 'react-redux'
    import { createStore } from 'redux'
    import reducer from './reducers';
    import TodoApp from ‘./components/TodoApp';
    const store = createStore(reducer);
    render(


    ,
    document.getElementById('root')
    )
    import { BrowserModule }
    from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import reducer from ‘./reducers';
    import TodoApp from ‘./components/TodoApp';
    @NgModule({
    imports: [
    BrowserModule,
    StoreModule.provideStore(reducer)
    ],
    bootstrap: [TodoApp]
    })
    export class AppModule { }
    import Vue from 'vue';
    import { createStore } from 'redux';
    import reducer from './reducers';
    import TodoApp from './components/TodoApp';
    const store = createStore(reducer);
    Vue.use({
    install: () =>
    Object.defineProperty(
    Vue.proprotype,
    '$store', {
    get() {
    return store;
    },
    }),
    });
    new Vue({
    el: '#root',
    render: h => h(TodoApp),
    });
    import React from 'react'
    import { createStore } from 'redux'
    import reducer from './reducers';
    const store = createStore(reducer);
    Bootstrap Redux Part

    View Slide

  111. import React from 'react'
    import { render } from 'react-dom'
    import { Provider } from 'react-redux'
    import { createStore } from 'redux'
    import reducer from './reducers';
    import TodoApp from ‘./components/TodoApp';
    const store = createStore(reducer);
    render(


    ,
    document.getElementById('root')
    )
    import { BrowserModule }
    from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import reducer from ‘./reducers';
    import TodoApp from ‘./components/TodoApp';
    @NgModule({
    imports: [
    BrowserModule,
    StoreModule.provideStore(reducer)
    ],
    bootstrap: [TodoApp]
    })
    export class AppModule { }
    import Vue from 'vue';
    import { createStore } from 'redux';
    import reducer from './reducers';
    import TodoApp from './components/TodoApp';
    const store = createStore(reducer);
    Vue.use({
    install: () =>
    Object.defineProperty(
    Vue.proprotype,
    '$store', {
    get() {
    return store;
    },
    }),
    });
    new Vue({
    el: '#root',
    render: h => h(TodoApp),
    });
    import React from 'react'
    import { createStore } from 'redux'
    import reducer from './reducers';
    const store = createStore(reducer);
    import reducer from ‘./reducers';
    Bootstrap Redux Part

    View Slide

  112. import React from 'react'
    import { render } from 'react-dom'
    import { Provider } from 'react-redux'
    import { createStore } from 'redux'
    import reducer from './reducers';
    import TodoApp from ‘./components/TodoApp';
    const store = createStore(reducer);
    render(


    ,
    document.getElementById('root')
    )
    import { BrowserModule }
    from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import reducer from ‘./reducers';
    import TodoApp from ‘./components/TodoApp';
    @NgModule({
    imports: [
    BrowserModule,
    StoreModule.provideStore(reducer)
    ],
    bootstrap: [TodoApp]
    })
    export class AppModule { }
    import Vue from 'vue';
    import { createStore } from 'redux';
    import reducer from './reducers';
    import TodoApp from './components/TodoApp';
    const store = createStore(reducer);
    Vue.use({
    install: () =>
    Object.defineProperty(
    Vue.proprotype,
    '$store', {
    get() {
    return store;
    },
    }),
    });
    new Vue({
    el: '#root',
    render: h => h(TodoApp),
    });
    import React from 'react'
    import { createStore } from 'redux'
    import reducer from './reducers';
    const store = createStore(reducer);
    import reducer from ‘./reducers';
    import { createStore } from 'redux';
    import reducer from './reducers';
    const store = createStore(reducer);
    Bootstrap Redux Part

    View Slide

  113. import React from 'react'
    import { render } from 'react-dom'
    import { Provider } from 'react-redux'
    import { createStore } from 'redux'
    import reducer from './reducers';
    import TodoApp from ‘./components/TodoApp';
    const store = createStore(reducer);
    render(


    ,
    document.getElementById('root')
    )
    import { BrowserModule }
    from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import reducer from ‘./reducers';
    import TodoApp from ‘./components/TodoApp';
    @NgModule({
    imports: [
    BrowserModule,
    StoreModule.provideStore(reducer)
    ],
    bootstrap: [TodoApp]
    })
    export class AppModule { }
    import Vue from 'vue';
    import { createStore } from 'redux';
    import reducer from './reducers';
    import TodoApp from './components/TodoApp';
    const store = createStore(reducer);
    Vue.use({
    install: () =>
    Object.defineProperty(
    Vue.proprotype,
    '$store', {
    get() {
    return store;
    },
    }),
    });
    new Vue({
    el: '#root',
    render: h => h(TodoApp),
    });
    import React from 'react'
    import { createStore } from 'redux'
    import reducer from './reducers';
    const store = createStore(reducer);
    import reducer from ‘./reducers';
    import { createStore } from 'redux';
    import reducer from './reducers';
    const store = createStore(reducer);
    Bootstrap Redux Part

    View Slide

  114. import React from 'react'
    import { render } from 'react-dom'
    import { Provider } from 'react-redux'
    import { createStore } from 'redux'
    import reducer from './reducers';
    import TodoApp from ‘./components/TodoApp';
    const store = createStore(reducer);
    render(


    ,
    document.getElementById('root')
    )
    import { BrowserModule }
    from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import reducer from ‘./reducers';
    import TodoApp from ‘./components/TodoApp';
    @NgModule({
    imports: [
    BrowserModule,
    StoreModule.provideStore(reducer)
    ],
    bootstrap: [TodoApp]
    })
    export class AppModule { }
    import Vue from 'vue';
    import { createStore } from 'redux';
    import reducer from './reducers';
    import TodoApp from './components/TodoApp';
    const store = createStore(reducer);
    Vue.use({
    install: () =>
    Object.defineProperty(
    Vue.proprotype,
    '$store', {
    get() {
    return store;
    },
    }),
    });
    new Vue({
    el: '#root',
    render: h => h(TodoApp),
    });
    Binding Redux to Framework

    View Slide

  115. import React from 'react'
    import { render } from 'react-dom'
    import { Provider } from 'react-redux'
    import { createStore } from 'redux'
    import reducer from './reducers';
    import TodoApp from ‘./components/TodoApp';
    const store = createStore(reducer);
    render(


    ,
    document.getElementById('root')
    )
    import { BrowserModule }
    from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import reducer from ‘./reducers';
    import TodoApp from ‘./components/TodoApp';
    @NgModule({
    imports: [
    BrowserModule,
    StoreModule.provideStore(reducer)
    ],
    bootstrap: [TodoApp]
    })
    export class AppModule { }
    import Vue from 'vue';
    import { createStore } from 'redux';
    import reducer from './reducers';
    import TodoApp from './components/TodoApp';
    const store = createStore(reducer);
    Vue.use({
    install: () =>
    Object.defineProperty(
    Vue.proprotype,
    '$store', {
    get() {
    return store;
    },
    }),
    });
    new Vue({
    el: '#root',
    render: h => h(TodoApp),
    });
    import { Provider } from 'react-redux'

    ,
    Binding Redux to Framework

    View Slide

  116. import React from 'react'
    import { render } from 'react-dom'
    import { Provider } from 'react-redux'
    import { createStore } from 'redux'
    import reducer from './reducers';
    import TodoApp from ‘./components/TodoApp';
    const store = createStore(reducer);
    render(


    ,
    document.getElementById('root')
    )
    import { BrowserModule }
    from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import reducer from ‘./reducers';
    import TodoApp from ‘./components/TodoApp';
    @NgModule({
    imports: [
    BrowserModule,
    StoreModule.provideStore(reducer)
    ],
    bootstrap: [TodoApp]
    })
    export class AppModule { }
    import Vue from 'vue';
    import { createStore } from 'redux';
    import reducer from './reducers';
    import TodoApp from './components/TodoApp';
    const store = createStore(reducer);
    Vue.use({
    install: () =>
    Object.defineProperty(
    Vue.proprotype,
    '$store', {
    get() {
    return store;
    },
    }),
    });
    new Vue({
    el: '#root',
    render: h => h(TodoApp),
    });
    import { Provider } from 'react-redux'

    ,
    import { StoreModule } from '@ngrx/store';
    StoreModule.provideStore(reducer)
    Binding Redux to Framework

    View Slide

  117. import React from 'react'
    import { render } from 'react-dom'
    import { Provider } from 'react-redux'
    import { createStore } from 'redux'
    import reducer from './reducers';
    import TodoApp from ‘./components/TodoApp';
    const store = createStore(reducer);
    render(


    ,
    document.getElementById('root')
    )
    import { BrowserModule }
    from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import reducer from ‘./reducers';
    import TodoApp from ‘./components/TodoApp';
    @NgModule({
    imports: [
    BrowserModule,
    StoreModule.provideStore(reducer)
    ],
    bootstrap: [TodoApp]
    })
    export class AppModule { }
    import Vue from 'vue';
    import { createStore } from 'redux';
    import reducer from './reducers';
    import TodoApp from './components/TodoApp';
    const store = createStore(reducer);
    Vue.use({
    install: () =>
    Object.defineProperty(
    Vue.proprotype,
    '$store', {
    get() {
    return store;
    },
    }),
    });
    new Vue({
    el: '#root',
    render: h => h(TodoApp),
    });
    import { Provider } from 'react-redux'

    ,
    import { StoreModule } from '@ngrx/store';
    StoreModule.provideStore(reducer)
    Vue.use({
    install: () =>
    Object.defineProperty(
    Vue.proprotype,
    '$store', {
    get() {
    return store;
    },
    }),
    });
    Binding Redux to Framework

    View Slide

  118. import React from 'react'
    import { render } from 'react-dom'
    import { Provider } from 'react-redux'
    import { createStore } from 'redux'
    import reducer from './reducers';
    import TodoApp from ‘./components/TodoApp';
    const store = createStore(reducer);
    render(


    ,
    document.getElementById('root')
    )
    import { BrowserModule }
    from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import reducer from ‘./reducers';
    import TodoApp from ‘./components/TodoApp';
    @NgModule({
    imports: [
    BrowserModule,
    StoreModule.provideStore(reducer)
    ],
    bootstrap: [TodoApp]
    })
    export class AppModule { }
    import Vue from 'vue';
    import { createStore } from 'redux';
    import reducer from './reducers';
    import TodoApp from './components/TodoApp';
    const store = createStore(reducer);
    Vue.use({
    install: () =>
    Object.defineProperty(
    Vue.proprotype,
    '$store', {
    get() {
    return store;
    },
    }),
    });
    new Vue({
    el: '#root',
    render: h => h(TodoApp),
    });
    import { Provider } from 'react-redux'

    ,
    import { StoreModule } from '@ngrx/store';
    StoreModule.provideStore(reducer)
    Vue.use({
    install: () =>
    Object.defineProperty(
    Vue.proprotype,
    '$store', {
    get() {
    return store;
    },
    }),
    });
    Binding Redux to Framework

    View Slide

  119. import React from 'react'
    import { render } from 'react-dom'
    import { Provider } from 'react-redux'
    import { createStore } from 'redux'
    import reducer from './reducers';
    import TodoApp from ‘./components/TodoApp';
    const store = createStore(reducer);
    render(


    ,
    document.getElementById('root')
    )
    import { BrowserModule }
    from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import reducer from ‘./reducers';
    import TodoApp from ‘./components/TodoApp';
    @NgModule({
    imports: [
    BrowserModule,
    StoreModule.provideStore(reducer)
    ],
    bootstrap: [TodoApp]
    })
    export class AppModule { }
    import Vue from 'vue';
    import { createStore } from 'redux';
    import reducer from './reducers';
    import TodoApp from './components/TodoApp';
    const store = createStore(reducer);
    Vue.use({
    install: () =>
    Object.defineProperty(
    Vue.proprotype,
    '$store', {
    get() {
    return store;
    },
    }),
    });
    new Vue({
    el: '#root',
    render: h => h(TodoApp),
    });
    Top Level Component

    View Slide

  120. import React from 'react'
    import { render } from 'react-dom'
    import { Provider } from 'react-redux'
    import { createStore } from 'redux'
    import reducer from './reducers';
    import TodoApp from ‘./components/TodoApp';
    const store = createStore(reducer);
    render(


    ,
    document.getElementById('root')
    )
    import { BrowserModule }
    from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import reducer from ‘./reducers';
    import TodoApp from ‘./components/TodoApp';
    @NgModule({
    imports: [
    BrowserModule,
    StoreModule.provideStore(reducer)
    ],
    bootstrap: [TodoApp]
    })
    export class AppModule { }
    import Vue from 'vue';
    import { createStore } from 'redux';
    import reducer from './reducers';
    import TodoApp from './components/TodoApp';
    const store = createStore(reducer);
    Vue.use({
    install: () =>
    Object.defineProperty(
    Vue.proprotype,
    '$store', {
    get() {
    return store;
    },
    }),
    });
    new Vue({
    el: '#root',
    render: h => h(TodoApp),
    });
    import TodoApp from ‘./components/TodoApp’;

    Top Level Component

    View Slide

  121. import React from 'react'
    import { render } from 'react-dom'
    import { Provider } from 'react-redux'
    import { createStore } from 'redux'
    import reducer from './reducers';
    import TodoApp from ‘./components/TodoApp';
    const store = createStore(reducer);
    render(


    ,
    document.getElementById('root')
    )
    import { BrowserModule }
    from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import reducer from ‘./reducers';
    import TodoApp from ‘./components/TodoApp';
    @NgModule({
    imports: [
    BrowserModule,
    StoreModule.provideStore(reducer)
    ],
    bootstrap: [TodoApp]
    })
    export class AppModule { }
    import Vue from 'vue';
    import { createStore } from 'redux';
    import reducer from './reducers';
    import TodoApp from './components/TodoApp';
    const store = createStore(reducer);
    Vue.use({
    install: () =>
    Object.defineProperty(
    Vue.proprotype,
    '$store', {
    get() {
    return store;
    },
    }),
    });
    new Vue({
    el: '#root',
    render: h => h(TodoApp),
    });
    import TodoApp from ‘./components/TodoApp’;

    import TodoApp from ‘./components/TodoApp';
    TodoApp
    Top Level Component

    View Slide

  122. import React from 'react'
    import { render } from 'react-dom'
    import { Provider } from 'react-redux'
    import { createStore } from 'redux'
    import reducer from './reducers';
    import TodoApp from ‘./components/TodoApp';
    const store = createStore(reducer);
    render(


    ,
    document.getElementById('root')
    )
    import { BrowserModule }
    from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import reducer from ‘./reducers';
    import TodoApp from ‘./components/TodoApp';
    @NgModule({
    imports: [
    BrowserModule,
    StoreModule.provideStore(reducer)
    ],
    bootstrap: [TodoApp]
    })
    export class AppModule { }
    import Vue from 'vue';
    import { createStore } from 'redux';
    import reducer from './reducers';
    import TodoApp from './components/TodoApp';
    const store = createStore(reducer);
    Vue.use({
    install: () =>
    Object.defineProperty(
    Vue.proprotype,
    '$store', {
    get() {
    return store;
    },
    }),
    });
    new Vue({
    el: '#root',
    render: h => h(TodoApp),
    });
    import TodoApp from ‘./components/TodoApp’;

    import TodoApp from ‘./components/TodoApp';
    TodoApp
    import TodoApp from './components/TodoApp';
    TodoApp
    Top Level Component

    View Slide

  123. import React from 'react'
    import { render } from 'react-dom'
    import { Provider } from 'react-redux'
    import { createStore } from 'redux'
    import reducer from './reducers';
    import TodoApp from ‘./components/TodoApp';
    const store = createStore(reducer);
    render(


    ,
    document.getElementById('root')
    )
    import { BrowserModule }
    from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import reducer from ‘./reducers';
    import TodoApp from ‘./components/TodoApp';
    @NgModule({
    imports: [
    BrowserModule,
    StoreModule.provideStore(reducer)
    ],
    bootstrap: [TodoApp]
    })
    export class AppModule { }
    import Vue from 'vue';
    import { createStore } from 'redux';
    import reducer from './reducers';
    import TodoApp from './components/TodoApp';
    const store = createStore(reducer);
    Vue.use({
    install: () =>
    Object.defineProperty(
    Vue.proprotype,
    '$store', {
    get() {
    return store;
    },
    }),
    });
    new Vue({
    el: '#root',
    render: h => h(TodoApp),
    });
    import TodoApp from ‘./components/TodoApp’;

    import TodoApp from ‘./components/TodoApp';
    TodoApp
    import TodoApp from './components/TodoApp';
    TodoApp
    Top Level Component

    View Slide

  124. Bootstrap

    ⛔ ⛔

    View Slide

  125. What about
    TodoApp.{jsx,ts,vue}

    View Slide

  126. View Slide

  127. import React, { Component } from 'react';
    import { connect } from 'react-redux';
    import * as actions from '../actions';
    const TodoApp = ({
    todos,
    addTodo,
    toggleTodo
    }) =>

    {todos.map(({ name, isCompleted }, index) =>

    type="checkbox"
    checked={isCompleted}
    onChange={() => toggleTodo(index)} />
    { name }

    )}
    onChange={evt => addTodo(evt.target.value)} />

    export default connect(state => state, actions)(
    TodoApp
    );
    import React, { Component } from 'react';
    import { connect } from 'react-redux';
    import * as actions from '../actions';
    const TodoApp = ({
    todos,
    addTodo,
    toggleTodo
    }) =>

    {todos.map(({ name, isCompleted }, index) =>

    type="checkbox"
    checked={isCompleted}
    onChange={() => toggleTodo(index)} />
    { name }

    )}
    onChange={evt => addTodo(evt.target.value)} />

    export default connect(state => state, actions)(
    TodoApp
    );

    View Slide

  128. import React, { Component } from 'react';
    import { connect } from 'react-redux';
    import * as actions from '../actions';
    const TodoApp = ({
    todos,
    addTodo,
    toggleTodo
    }) =>

    {todos.map(({ name, isCompleted }, index) =>

    type="checkbox"
    checked={isCompleted}
    onChange={() => toggleTodo(index)} />
    { name }

    )}
    onChange={evt => addTodo(evt.target.value)} />

    export default connect(state => state, actions)(
    TodoApp
    );
    import { Component } from '@angular/core';
    import { Store } from '@ngrx/store';
    import * as actions from '../actions';
    @Component({
    selector: 'app-board',
    template: `


    [checked]="todo.isCompleted"
    (change)="store.dispatch(
    actions.toggleTodo(index)
    )" />
    {{ todo.name }}

    (change)="store.dispatch(
    actions.addTodo($event.target.value)
    )" />
    `
    })
    export class BoardComponent {
    constructor(store: Store){
    this.store = store;
    this.actions = actions;
    store.subscribe(state => { this.todos =
    state.todos; })
    }
    }
    import React, { Component } from 'react';
    import { connect } from 'react-redux';
    import * as actions from '../actions';
    const TodoApp = ({
    todos,
    addTodo,
    toggleTodo
    }) =>

    {todos.map(({ name, isCompleted }, index) =>

    type="checkbox"
    checked={isCompleted}
    onChange={() => toggleTodo(index)} />
    { name }

    )}
    onChange={evt => addTodo(evt.target.value)} />

    export default connect(state => state, actions)(
    TodoApp
    );
    import { Component } from '@angular/core';
    import { Store } from '@ngrx/store';
    import * as actions from '../actions';
    @Component({
    selector: 'app-board',
    template: `


    [checked]="todo.isCompleted"
    (change)="store.dispatch(
    actions.toggleTodo(index)
    )" />
    {{ todo.name }}

    (change)="store.dispatch(
    actions.addTodo($event.target.value)
    )" />
    `
    })
    export class BoardComponent {
    constructor(store: Store){
    this.store = store;
    this.actions = actions;
    store.subscribe(state => { this.todos =
    state.todos; })
    }
    }

    View Slide

  129. import React, { Component } from 'react';
    import { connect } from 'react-redux';
    import * as actions from '../actions';
    const TodoApp = ({
    todos,
    addTodo,
    toggleTodo
    }) =>

    {todos.map(({ name, isCompleted }, index) =>

    type="checkbox"
    checked={isCompleted}
    onChange={() => toggleTodo(index)} />
    { name }

    )}
    onChange={evt => addTodo(evt.target.value)} />

    export default connect(state => state, actions)(
    TodoApp
    );
    import { Component } from '@angular/core';
    import { Store } from '@ngrx/store';
    import * as actions from '../actions';
    @Component({
    selector: 'app-board',
    template: `


    [checked]="todo.isCompleted"
    (change)="store.dispatch(
    actions.toggleTodo(index)
    )" />
    {{ todo.name }}

    (change)="store.dispatch(
    actions.addTodo($event.target.value)
    )" />
    `
    })
    export class BoardComponent {
    constructor(store: Store){
    this.store = store;
    this.actions = actions;
    store.subscribe(state => { this.todos =
    state.todos; })
    }
    }



    :checked="todo.isCompleted"
    @change="() => toggleTodo(index)" />
    {{ todo.name }}

    @change="(evt) => addTodo(evt.target.value)" />


    <br/>import * as actions from "../../common/actions";<br/>import { connect } from "../store";<br/>export default {<br/>mixins: [connect(state => state, actions)]<br/>};<br/>
    import React, { Component } from 'react';
    import { connect } from 'react-redux';
    import * as actions from '../actions';
    const TodoApp = ({
    todos,
    addTodo,
    toggleTodo
    }) =>

    {todos.map(({ name, isCompleted }, index) =>

    type="checkbox"
    checked={isCompleted}
    onChange={() => toggleTodo(index)} />
    { name }

    )}
    onChange={evt => addTodo(evt.target.value)} />

    export default connect(state => state, actions)(
    TodoApp
    );
    import { Component } from '@angular/core';
    import { Store } from '@ngrx/store';
    import * as actions from '../actions';
    @Component({
    selector: 'app-board',
    template: `


    [checked]="todo.isCompleted"
    (change)="store.dispatch(
    actions.toggleTodo(index)
    )" />
    {{ todo.name }}

    (change)="store.dispatch(
    actions.addTodo($event.target.value)
    )" />
    `
    })
    export class BoardComponent {
    constructor(store: Store){
    this.store = store;
    this.actions = actions;
    store.subscribe(state => { this.todos =
    state.todos; })
    }
    }



    :checked="todo.isCompleted"
    @change="() => toggleTodo(index)" />
    {{ todo.name }}

    @change="(evt) => addTodo(evt.target.value)" />


    <br/>import * as actions from "../../common/actions";<br/>import { connect } from "../store";<br/>export default {<br/>mixins: [connect(state => state, actions)]<br/>};<br/>

    View Slide

  130. import React, { Component } from 'react';
    import { connect } from 'react-redux';
    import * as actions from '../actions';
    const TodoApp = ({
    todos,
    addTodo,
    toggleTodo
    }) =>

    {todos.map(({ name, isCompleted }, index) =>

    type="checkbox"
    checked={isCompleted}
    onChange={() => toggleTodo(index)} />
    { name }

    )}
    onChange={evt => addTodo(evt.target.value)} />

    export default connect(state => state, actions)(
    TodoApp
    );
    import { Component } from '@angular/core';
    import { Store } from '@ngrx/store';
    import * as actions from '../actions';
    @Component({
    selector: 'app-board',
    template: `


    [checked]="todo.isCompleted"
    (change)="store.dispatch(
    actions.toggleTodo(index)
    )" />
    {{ todo.name }}

    (change)="store.dispatch(
    actions.addTodo($event.target.value)
    )" />
    `
    })
    export class BoardComponent {
    constructor(store: Store){
    this.store = store;
    this.actions = actions;
    store.subscribe(state => { this.todos =
    state.todos; })
    }
    }



    :checked="todo.isCompleted"
    @change="() => toggleTodo(index)" />
    {{ todo.name }}

    @change="(evt) => addTodo(evt.target.value)" />


    <br/>import * as actions from "../../common/actions";<br/>import { connect } from "../store";<br/>export default {<br/>mixins: [connect(state => state, actions)]<br/>};<br/>
    Component Definition

    View Slide

  131. import React, { Component } from 'react';
    import { connect } from 'react-redux';
    import * as actions from '../actions';
    const TodoApp = ({
    todos,
    addTodo,
    toggleTodo
    }) =>

    {todos.map(({ name, isCompleted }, index) =>

    type="checkbox"
    checked={isCompleted}
    onChange={() => toggleTodo(index)} />
    { name }

    )}
    onChange={evt => addTodo(evt.target.value)} />

    export default connect(state => state, actions)(
    TodoApp
    );
    import { Component } from '@angular/core';
    import { Store } from '@ngrx/store';
    import * as actions from '../actions';
    @Component({
    selector: 'app-board',
    template: `


    [checked]="todo.isCompleted"
    (change)="store.dispatch(
    actions.toggleTodo(index)
    )" />
    {{ todo.name }}

    (change)="store.dispatch(
    actions.addTodo($event.target.value)
    )" />
    `
    })
    export class BoardComponent {
    constructor(store: Store){
    this.store = store;
    this.actions = actions;
    store.subscribe(state => { this.todos =
    state.todos; })
    }
    }



    :checked="todo.isCompleted"
    @change="() => toggleTodo(index)" />
    {{ todo.name }}

    @change="(evt) => addTodo(evt.target.value)" />


    <br/>import * as actions from "../../common/actions";<br/>import { connect } from "../store";<br/>export default {<br/>mixins: [connect(state => state, actions)]<br/>};<br/>
    import React, { Component } from 'react';
    const TodoApp = ({
    todos,
    addTodo,
    toggleTodo
    }) =>


    export default (
    TodoApp
    );
    Component Definition

    View Slide

  132. import React, { Component } from 'react';
    import { connect } from 'react-redux';
    import * as actions from '../actions';
    const TodoApp = ({
    todos,
    addTodo,
    toggleTodo
    }) =>

    {todos.map(({ name, isCompleted }, index) =>

    type="checkbox"
    checked={isCompleted}
    onChange={() => toggleTodo(index)} />
    { name }

    )}
    onChange={evt => addTodo(evt.target.value)} />

    export default connect(state => state, actions)(
    TodoApp
    );
    import { Component } from '@angular/core';
    import { Store } from '@ngrx/store';
    import * as actions from '../actions';
    @Component({
    selector: 'app-board',
    template: `


    [checked]="todo.isCompleted"
    (change)="store.dispatch(
    actions.toggleTodo(index)
    )" />
    {{ todo.name }}

    (change)="store.dispatch(
    actions.addTodo($event.target.value)
    )" />
    `
    })
    export class BoardComponent {
    constructor(store: Store){
    this.store = store;
    this.actions = actions;
    store.subscribe(state => { this.todos =
    state.todos; })
    }
    }



    :checked="todo.isCompleted"
    @change="() => toggleTodo(index)" />
    {{ todo.name }}

    @change="(evt) => addTodo(evt.target.value)" />


    <br/>import * as actions from "../../common/actions";<br/>import { connect } from "../store";<br/>export default {<br/>mixins: [connect(state => state, actions)]<br/>};<br/>
    import React, { Component } from 'react';
    const TodoApp = ({
    todos,
    addTodo,
    toggleTodo
    }) =>


    export default (
    TodoApp
    );
    import { Component } from '@angular/core';
    @Component({
    selector: 'app-board',
    template: `

    `
    })
    export class BoardComponent {
    constructor( ){
    }
    }
    Component Definition

    View Slide

  133. import React, { Component } from 'react';
    import { connect } from 'react-redux';
    import * as actions from '../actions';
    const TodoApp = ({
    todos,
    addTodo,
    toggleTodo
    }) =>

    {todos.map(({ name, isCompleted }, index) =>

    type="checkbox"
    checked={isCompleted}
    onChange={() => toggleTodo(index)} />
    { name }

    )}
    onChange={evt => addTodo(evt.target.value)} />

    export default connect(state => state, actions)(
    TodoApp
    );
    import { Component } from '@angular/core';
    import { Store } from '@ngrx/store';
    import * as actions from '../actions';
    @Component({
    selector: 'app-board',
    template: `


    [checked]="todo.isCompleted"
    (change)="store.dispatch(
    actions.toggleTodo(index)
    )" />
    {{ todo.name }}

    (change)="store.dispatch(
    actions.addTodo($event.target.value)
    )" />
    `
    })
    export class BoardComponent {
    constructor(store: Store){
    this.store = store;
    this.actions = actions;
    store.subscribe(state => { this.todos =
    state.todos; })
    }
    }



    :checked="todo.isCompleted"
    @change="() => toggleTodo(index)" />
    {{ todo.name }}

    @change="(evt) => addTodo(evt.target.value)" />


    <br/>import * as actions from "../../common/actions";<br/>import { connect } from "../store";<br/>export default {<br/>mixins: [connect(state => state, actions)]<br/>};<br/>
    import React, { Component } from 'react';
    const TodoApp = ({
    todos,
    addTodo,
    toggleTodo
    }) =>


    export default (
    TodoApp
    );
    import { Component } from '@angular/core';
    @Component({
    selector: 'app-board',
    template: `

    `
    })
    export class BoardComponent {
    constructor( ){
    }
    }




    <br/>export default {<br/>};<br/>
    Component Definition

    View Slide

  134. import React, { Component } from 'react';
    import { connect } from 'react-redux';
    import * as actions from '../actions';
    const TodoApp = ({
    todos,
    addTodo,
    toggleTodo
    }) =>

    {todos.map(({ name, isCompleted }, index) =>

    type="checkbox"
    checked={isCompleted}
    onChange={() => toggleTodo(index)} />
    { name }

    )}
    onChange={evt => addTodo(evt.target.value)} />

    export default connect(state => state, actions)(
    TodoApp
    );
    import { Component } from '@angular/core';
    import { Store } from '@ngrx/store';
    import * as actions from '../actions';
    @Component({
    selector: 'app-board',
    template: `


    [checked]="todo.isCompleted"
    (change)="store.dispatch(
    actions.toggleTodo(index)
    )" />
    {{ todo.name }}

    (change)="store.dispatch(
    actions.addTodo($event.target.value)
    )" />
    `
    })
    export class BoardComponent {
    constructor(store: Store){
    this.store = store;
    this.actions = actions;
    store.subscribe(state => { this.todos =
    state.todos; })
    }
    }



    :checked="todo.isCompleted"
    @change="() => toggleTodo(index)" />
    {{ todo.name }}

    @change="(evt) => addTodo(evt.target.value)" />


    <br/>import * as actions from "../../common/actions";<br/>import { connect } from "../store";<br/>export default {<br/>mixins: [connect(state => state, actions)]<br/>};<br/>
    import React, { Component } from 'react';
    const TodoApp = ({
    todos,
    addTodo,
    toggleTodo
    }) =>


    export default (
    TodoApp
    );
    import { Component } from '@angular/core';
    @Component({
    selector: 'app-board',
    template: `

    `
    })
    export class BoardComponent {
    constructor( ){
    }
    }




    <br/>export default {<br/>};<br/>
    Component Definition

    View Slide

  135. import React, { Component } from 'react';
    import { connect } from 'react-redux';
    import * as actions from '../actions';
    const TodoApp = ({
    todos,
    addTodo,
    toggleTodo
    }) =>

    {todos.map(({ name, isCompleted }, index) =>

    type="checkbox"
    checked={isCompleted}
    onChange={() => toggleTodo(index)} />
    { name }

    )}
    onChange={evt => addTodo(evt.target.value)} />

    export default connect(state => state, actions)(
    TodoApp
    );
    import { Component } from '@angular/core';
    import { Store } from '@ngrx/store';
    import * as actions from '../actions';
    @Component({
    selector: 'app-board',
    template: `


    [checked]="todo.isCompleted"
    (change)="store.dispatch(
    actions.toggleTodo(index)
    )" />
    {{ todo.name }}

    (change)="store.dispatch(
    actions.addTodo($event.target.value)
    )" />
    `
    })
    export class BoardComponent {
    constructor(store: Store){
    this.store = store;
    this.actions = actions;
    store.subscribe(state => { this.todos =
    state.todos; })
    }
    }



    :checked="todo.isCompleted"
    @change="() => toggleTodo(index)" />
    {{ todo.name }}

    @change="(evt) => addTodo(evt.target.value)" />


    <br/>import * as actions from "../../common/actions";<br/>import { connect } from "../store";<br/>export default {<br/>mixins: [connect(state => state, actions)]<br/>};<br/>
    Binding Redux to Component

    View Slide

  136. import React, { Component } from 'react';
    import { connect } from 'react-redux';
    import * as actions from '../actions';
    const TodoApp = ({
    todos,
    addTodo,
    toggleTodo
    }) =>

    {todos.map(({ name, isCompleted }, index) =>

    type="checkbox"
    checked={isCompleted}
    onChange={() => toggleTodo(index)} />
    { name }

    )}
    onChange={evt => addTodo(evt.target.value)} />

    export default connect(state => state, actions)(
    TodoApp
    );
    import { Component } from '@angular/core';
    import { Store } from '@ngrx/store';
    import * as actions from '../actions';
    @Component({
    selector: 'app-board',
    template: `


    [checked]="todo.isCompleted"
    (change)="store.dispatch(
    actions.toggleTodo(index)
    )" />
    {{ todo.name }}

    (change)="store.dispatch(
    actions.addTodo($event.target.value)
    )" />
    `
    })
    export class BoardComponent {
    constructor(store: Store){
    this.store = store;
    this.actions = actions;
    store.subscribe(state => { this.todos =
    state.todos; })
    }
    }



    :checked="todo.isCompleted"
    @change="() => toggleTodo(index)" />
    {{ todo.name }}

    @change="(evt) => addTodo(evt.target.value)" />


    <br/>import * as actions from "../../common/actions";<br/>import { connect } from "../store";<br/>export default {<br/>mixins: [connect(state => state, actions)]<br/>};<br/>
    import { connect } from 'react-redux';
    import * as actions from '../actions';
    export default connect(state => state, actions)(
    );
    Binding Redux to Component

    View Slide

  137. import React, { Component } from 'react';
    import { connect } from 'react-redux';
    import * as actions from '../actions';
    const TodoApp = ({
    todos,
    addTodo,
    toggleTodo
    }) =>

    {todos.map(({ name, isCompleted }, index) =>

    type="checkbox"
    checked={isCompleted}
    onChange={() => toggleTodo(index)} />
    { name }

    )}
    onChange={evt => addTodo(evt.target.value)} />

    export default connect(state => state, actions)(
    TodoApp
    );
    import { Component } from '@angular/core';
    import { Store } from '@ngrx/store';
    import * as actions from '../actions';
    @Component({
    selector: 'app-board',
    template: `


    [checked]="todo.isCompleted"
    (change)="store.dispatch(
    actions.toggleTodo(index)
    )" />
    {{ todo.name }}

    (change)="store.dispatch(
    actions.addTodo($event.target.value)
    )" />
    `
    })
    export class BoardComponent {
    constructor(store: Store){
    this.store = store;
    this.actions = actions;
    store.subscribe(state => { this.todos =
    state.todos; })
    }
    }



    :checked="todo.isCompleted"
    @change="() => toggleTodo(index)" />
    {{ todo.name }}

    @change="(evt) => addTodo(evt.target.value)" />


    <br/>import * as actions from "../../common/actions";<br/>import { connect } from "../store";<br/>export default {<br/>mixins: [connect(state => state, actions)]<br/>};<br/>
    import { connect } from 'react-redux';
    import * as actions from '../actions';
    export default connect(state => state, actions)(
    );
    import { Store } from '@ngrx/store';
    import * as actions from '../actions';
    store: Store
    this.store = store;
    this.actions = actions;
    store.subscribe(state => { this.todos =
    state.todos; })
    }
    }
    Binding Redux to Component

    View Slide

  138. import React, { Component } from 'react';
    import { connect } from 'react-redux';
    import * as actions from '../actions';
    const TodoApp = ({
    todos,
    addTodo,
    toggleTodo
    }) =>

    {todos.map(({ name, isCompleted }, index) =>

    type="checkbox"
    checked={isCompleted}
    onChange={() => toggleTodo(index)} />
    { name }

    )}
    onChange={evt => addTodo(evt.target.value)} />

    export default connect(state => state, actions)(
    TodoApp
    );
    import { Component } from '@angular/core';
    import { Store } from '@ngrx/store';
    import * as actions from '../actions';
    @Component({
    selector: 'app-board',
    template: `


    [checked]="todo.isCompleted"
    (change)="store.dispatch(
    actions.toggleTodo(index)
    )" />
    {{ todo.name }}

    (change)="store.dispatch(
    actions.addTodo($event.target.value)
    )" />
    `
    })
    export class BoardComponent {
    constructor(store: Store){
    this.store = store;
    this.actions = actions;
    store.subscribe(state => { this.todos =
    state.todos; })
    }
    }



    :checked="todo.isCompleted"
    @change="() => toggleTodo(index)" />
    {{ todo.name }}

    @change="(evt) => addTodo(evt.target.value)" />


    <br/>import * as actions from "../../common/actions";<br/>import { connect } from "../store";<br/>export default {<br/>mixins: [connect(state => state, actions)]<br/>};<br/>
    import { connect } from 'react-redux';
    import * as actions from '../actions';
    export default connect(state => state, actions)(
    );
    import { Store } from '@ngrx/store';
    import * as actions from '../actions';
    store: Store
    this.store = store;
    this.actions = actions;
    store.subscribe(state => { this.todos =
    state.todos; })
    }
    }
    import * as actions from "../../common/actions";
    import { connect } from "../store";
    mixins: [connect(state => state, actions)]
    Binding Redux to Component

    View Slide

  139. import React, { Component } from 'react';
    import { connect } from 'react-redux';
    import * as actions from '../actions';
    const TodoApp = ({
    todos,
    addTodo,
    toggleTodo
    }) =>

    {todos.map(({ name, isCompleted }, index) =>

    type="checkbox"
    checked={isCompleted}
    onChange={() => toggleTodo(index)} />
    { name }

    )}
    onChange={evt => addTodo(evt.target.value)} />

    export default connect(state => state, actions)(
    TodoApp
    );
    import { Component } from '@angular/core';
    import { Store } from '@ngrx/store';
    import * as actions from '../actions';
    @Component({
    selector: 'app-board',
    template: `


    [checked]="todo.isCompleted"
    (change)="store.dispatch(
    actions.toggleTodo(index)
    )" />
    {{ todo.name }}

    (change)="store.dispatch(
    actions.addTodo($event.target.value)
    )" />
    `
    })
    export class BoardComponent {
    constructor(store: Store){
    this.store = store;
    this.actions = actions;
    store.subscribe(state => { this.todos =
    state.todos; })
    }
    }



    :checked="todo.isCompleted"
    @change="() => toggleTodo(index)" />
    {{ todo.name }}

    @change="(evt) => addTodo(evt.target.value)" />


    <br/>import * as actions from "../../common/actions";<br/>import { connect } from "../store";<br/>export default {<br/>mixins: [connect(state => state, actions)]<br/>};<br/>
    import { connect } from 'react-redux';
    import * as actions from '../actions';
    export default connect(state => state, actions)(
    );
    import { Store } from '@ngrx/store';
    import * as actions from '../actions';
    store: Store
    this.store = store;
    this.actions = actions;
    store.subscribe(state => { this.todos =
    state.todos; })
    }
    }
    import * as actions from "../../common/actions";
    import { connect } from "../store";
    mixins: [connect(state => state, actions)]
    Binding Redux to Component

    View Slide

  140. import React, { Component } from 'react';
    import { connect } from 'react-redux';
    import * as actions from '../actions';
    const TodoApp = ({
    todos,
    addTodo,
    toggleTodo
    }) =>

    {todos.map(({ name, isCompleted }, index) =>

    type="checkbox"
    checked={isCompleted}
    onChange={() => toggleTodo(index)} />
    { name }

    )}
    onChange={evt => addTodo(evt.target.value)} />

    export default connect(state => state, actions)(
    TodoApp
    );
    import { Component } from '@angular/core';
    import { Store } from '@ngrx/store';
    import * as actions from '../actions';
    @Component({
    selector: 'app-board',
    template: `


    [checked]="todo.isCompleted"
    (change)="store.dispatch(
    actions.toggleTodo(index)
    )" />
    {{ todo.name }}

    (change)="store.dispatch(
    actions.addTodo($event.target.value)
    )" />
    `
    })
    export class BoardComponent {
    constructor(store: Store){
    this.store = store;
    this.actions = actions;
    store.subscribe(state => { this.todos =
    state.todos; })
    }
    }



    :checked="todo.isCompleted"
    @change="() => toggleTodo(index)" />
    {{ todo.name }}

    @change="(evt) => addTodo(evt.target.value)" />


    <br/>import * as actions from "../../common/actions";<br/>import { connect } from "../store";<br/>export default {<br/>mixins: [connect(state => state, actions)]<br/>};<br/>
    Component “Template”

    View Slide

  141. import React, { Component } from 'react';
    import { connect } from 'react-redux';
    import * as actions from '../actions';
    const TodoApp = ({
    todos,
    addTodo,
    toggleTodo
    }) =>

    {todos.map(({ name, isCompleted }, index) =>

    type="checkbox"
    checked={isCompleted}
    onChange={() => toggleTodo(index)} />
    { name }

    )}
    onChange={evt => addTodo(evt.target.value)} />

    export default connect(state => state, actions)(
    TodoApp
    );
    import { Component } from '@angular/core';
    import { Store } from '@ngrx/store';
    import * as actions from '../actions';
    @Component({
    selector: 'app-board',
    template: `


    [checked]="todo.isCompleted"
    (change)="store.dispatch(
    actions.toggleTodo(index)
    )" />
    {{ todo.name }}

    (change)="store.dispatch(
    actions.addTodo($event.target.value)
    )" />
    `
    })
    export class BoardComponent {
    constructor(store: Store){
    this.store = store;
    this.actions = actions;
    store.subscribe(state => { this.todos =
    state.todos; })
    }
    }



    :checked="todo.isCompleted"
    @change="() => toggleTodo(index)" />
    {{ todo.name }}

    @change="(evt) => addTodo(evt.target.value)" />


    <br/>import * as actions from "../../common/actions";<br/>import { connect } from "../store";<br/>export default {<br/>mixins: [connect(state => state, actions)]<br/>};<br/>

    {todos.map(({ name, isCompleted }, index) =>

    type="checkbox"
    checked={isCompleted}
    onChange={() => toggleTodo(index)} />
    { name }

    )}
    onChange={evt => addTodo(evt.target.value)} />

    Component “Template”

    View Slide

  142. import React, { Component } from 'react';
    import { connect } from 'react-redux';
    import * as actions from '../actions';
    const TodoApp = ({
    todos,
    addTodo,
    toggleTodo
    }) =>

    {todos.map(({ name, isCompleted }, index) =>

    type="checkbox"
    checked={isCompleted}
    onChange={() => toggleTodo(index)} />
    { name }

    )}
    onChange={evt => addTodo(evt.target.value)} />

    export default connect(state => state, actions)(
    TodoApp
    );
    import { Component } from '@angular/core';
    import { Store } from '@ngrx/store';
    import * as actions from '../actions';
    @Component({
    selector: 'app-board',
    template: `


    [checked]="todo.isCompleted"
    (change)="store.dispatch(
    actions.toggleTodo(index)
    )" />
    {{ todo.name }}

    (change)="store.dispatch(
    actions.addTodo($event.target.value)
    )" />
    `
    })
    export class BoardComponent {
    constructor(store: Store){
    this.store = store;
    this.actions = actions;
    store.subscribe(state => { this.todos =
    state.todos; })
    }
    }



    :checked="todo.isCompleted"
    @change="() => toggleTodo(index)" />
    {{ todo.name }}

    @change="(evt) => addTodo(evt.target.value)" />


    <br/>import * as actions from "../../common/actions";<br/>import { connect } from "../store";<br/>export default {<br/>mixins: [connect(state => state, actions)]<br/>};<br/>

    {todos.map(({ name, isCompleted }, index) =>

    type="checkbox"
    checked={isCompleted}
    onChange={() => toggleTodo(index)} />
    { name }

    )}
    onChange={evt => addTodo(evt.target.value)} />



    [checked]="todo.isCompleted"
    (change)="store.dispatch(
    actions.toggleTodo(index)
    )" />
    {{ todo.name }}

    (change)="store.dispatch(
    actions.addTodo($event.target.value)
    )" />
    `
    Component “Template”

    View Slide

  143. import React, { Component } from 'react';
    import { connect } from 'react-redux';
    import * as actions from '../actions';
    const TodoApp = ({
    todos,
    addTodo,
    toggleTodo
    }) =>

    {todos.map(({ name, isCompleted }, index) =>

    type="checkbox"
    checked={isCompleted}
    onChange={() => toggleTodo(index)} />
    { name }

    )}
    onChange={evt => addTodo(evt.target.value)} />

    export default connect(state => state, actions)(
    TodoApp
    );
    import { Component } from '@angular/core';
    import { Store } from '@ngrx/store';
    import * as actions from '../actions';
    @Component({
    selector: 'app-board',
    template: `


    [checked]="todo.isCompleted"
    (change)="store.dispatch(
    actions.toggleTodo(index)
    )" />
    {{ todo.name }}

    (change)="store.dispatch(
    actions.addTodo($event.target.value)
    )" />
    `
    })
    export class BoardComponent {
    constructor(store: Store){
    this.store = store;
    this.actions = actions;
    store.subscribe(state => { this.todos =
    state.todos; })
    }
    }



    :checked="todo.isCompleted"
    @change="() => toggleTodo(index)" />
    {{ todo.name }}

    @change="(evt) => addTodo(evt.target.value)" />


    <br/>import * as actions from "../../common/actions";<br/>import { connect } from "../store";<br/>export default {<br/>mixins: [connect(state => state, actions)]<br/>};<br/>

    {todos.map(({ name, isCompleted }, index) =>

    type="checkbox"
    checked={isCompleted}
    onChange={() => toggleTodo(index)} />
    { name }

    )}
    onChange={evt => addTodo(evt.target.value)} />



    [checked]="todo.isCompleted"
    (change)="store.dispatch(
    actions.toggleTodo(index)
    )" />
    {{ todo.name }}

    (change)="store.dispatch(
    actions.addTodo($event.target.value)
    )" />
    `



    :checked="todo.isCompleted"
    @change="() => toggleTodo(index)" />
    {{ todo.name }}

    @change="(evt) => addTodo(evt.target.value)" />


    Component “Template”

    View Slide

  144. import React, { Component } from 'react';
    import { connect } from 'react-redux';
    import * as actions from '../actions';
    const TodoApp = ({
    todos,
    addTodo,
    toggleTodo
    }) =>

    {todos.map(({ name, isCompleted }, index) =>

    type="checkbox"
    checked={isCompleted}
    onChange={() => toggleTodo(index)} />
    { name }

    )}
    onChange={evt => addTodo(evt.target.value)} />

    export default connect(state => state, actions)(
    TodoApp
    );
    import { Component } from '@angular/core';
    import { Store } from '@ngrx/store';
    import * as actions from '../actions';
    @Component({
    selector: 'app-board',
    template: `


    [checked]="todo.isCompleted"
    (change)="store.dispatch(
    actions.toggleTodo(index)
    )" />
    {{ todo.name }}

    (change)="store.dispatch(
    actions.addTodo($event.target.value)
    )" />
    `
    })
    export class BoardComponent {
    constructor(store: Store){
    this.store = store;
    this.actions = actions;
    store.subscribe(state => { this.todos =
    state.todos; })
    }
    }



    :checked="todo.isCompleted"
    @change="() => toggleTodo(index)" />
    {{ todo.name }}

    @change="(evt) => addTodo(evt.target.value)" />


    <br/>import * as actions from "../../common/actions";<br/>import { connect } from "../store";<br/>export default {<br/>mixins: [connect(state => state, actions)]<br/>};<br/>

    {todos.map(({ name, isCompleted }, index) =>

    type="checkbox"
    checked={isCompleted}
    onChange={() => toggleTodo(index)} />
    { name }

    )}
    onChange={evt => addTodo(evt.target.value)} />



    [checked]="todo.isCompleted"
    (change)="store.dispatch(
    actions.toggleTodo(index)
    )" />
    {{ todo.name }}

    (change)="store.dispatch(
    actions.addTodo($event.target.value)
    )" />
    `



    :checked="todo.isCompleted"
    @change="() => toggleTodo(index)" />
    {{ todo.name }}

    @change="(evt) => addTodo(evt.target.value)" />


    Component “Template”

    View Slide

  145. Agnostic Components

    View Slide

  146. We can do
    BETTER.

    View Slide

  147. Write a trello clone redux application and use in React, Vue and
    Angular 2 without code changes.
    CHALLENGE #2

    View Slide

  148. View Slide

  149. const columnsState = {
    "dd933cd3-842f-4546-b2b2-94851eb2e89c": {
    index: 0,
    name: "todo"
    },
    "f9c9e0ff-6f54-4787-bbb4-b467318fffc7": {
    index: 1,
    name: "doing"
    },
    "d8ee1c37-7c21-4754-9ebc-1c73b3eb3a8d": {
    index: 2,
    name: "done"
    }
    };
    Step 1: design state

    View Slide

  150. {
    "47749e5": {
    name: "Todo"
    },
    "dde4872": {
    name: "Doing"
    },
    "ac32fcc": {
    name: "Done"
    }
    }
    Normalized State

    View Slide

  151. {
    "47749e5": {
    name: "Todo"
    },
    "dde4872": {
    name: "Doing"
    },
    "ac32fcc": {
    name: "Done"
    }
    }
    Normalized State
    const arrayfyObject = obj =>
    Object
    .keys(obj)
    .map(id => ({
    ...obj[id],
    id
    }));
    A simple selector

    View Slide

  152. {
    "47749e5": {
    name: "Todo"
    },
    "dde4872": {
    name: "Doing"
    },
    "ac32fcc": {
    name: "Done"
    }
    }
    [
    {
    id: "47749e5",
    name: "Todo"
    },
    {
    id: "dde4872",
    name: "Doing"
    },
    {
    id: "ac32fcc",
    name: "Done"
    }
    ]
    Normalized State De-Normalized State
    const arrayfyObject = obj =>
    Object
    .keys(obj)
    .map(id => ({
    ...obj[id],
    id
    }));
    A simple selector

    View Slide

  153. const tasksState = {
    "542e1d5b-db3a-4910-a103-78b55488198c": {
    column: "dd933cd3-842f-4546-b2b2-94851eb2e89c",
    assignee: null,
    name: "task 1"
    },
    "33255381-182d-4183-b5b7-b7cfd409f583": {
    column: "f9c9e0ff-6f54-4787-bbb4-b467318fffc7",
    assignee: null,
    name: "task 2"
    }
    };
    Step 1: design state

    View Slide

  154. const tasksState = {
    "542e1d5b-db3a-4910-a103-78b55488198c": {
    column: "dd933cd3-842f-4546-b2b2-94851eb2e89c",
    assignee: null,
    name: "task 1"
    },
    "33255381-182d-4183-b5b7-b7cfd409f583": {
    column: "f9c9e0ff-6f54-4787-bbb4-b467318fffc7",
    assignee: null,
    name: "task 2"
    }
    };
    Step 1: design state
    column: "dd933cd3-842f-4546-b2b2-94851eb2e89c"
    column: “f9c9e0ff-6f54-4787-bbb4-b467318fffc7"
    };

    View Slide

  155. Step 2: actions
    // actions/columns.js
    export const RENAME_COLUMUN = "RENAME_COLUMUN";
    export const renameColumn = (id, name) => ({
    type: RENAME_COLUMUN,
    payload: { id, name }
    });

    View Slide

  156. // actions/tasks.js
    export const ADD_TASK_TO_COLUMN = "ADD_TASK_TO_COLUMN";
    export const addTaskToColumn = (columnId, userId) => ({
    type: ADD_TASK_TO_COLUMN,
    payload: { columnId, userId }
    });
    Step 2: actions

    View Slide

  157. Step 3: reducers
    // reducers/columns.js
    export default function columns(state = {}, { type, payload }) {
    if (type === RENAME_COLUMUN) {
    return {
    ...state,
    [payload.id]: {
    ...state[payload.id],
    name: payload.name
    }
    };
    }
    return state;
    }

    View Slide

  158. columns(
    kanbanState,
    renameColumn("ac32fcc", "Shipped"));

    View Slide

  159. {
    "47749e5": {
    name: "Todo"
    },
    "dde4872": {
    name: "Doing"
    },
    "ac32fcc": {
    name: "Done"
    }
    }
    columns(
    kanbanState,
    renameColumn("ac32fcc", "Shipped"));
    {
    "47749e5": {
    name: "Todo"
    },
    "dde4872": {
    name: "Doing"
    },
    "ac32fcc": {
    name: "Done"
    }
    }

    View Slide

  160. {
    "47749e5": {
    name: "Todo"
    },
    "dde4872": {
    name: "Doing"
    },
    "ac32fcc": {
    name: "Done"
    }
    }
    columns(
    kanbanState,
    renameColumn("ac32fcc", "Shipped"));
    "ac32fcc": {
    name: "Done"
    }

    View Slide

  161. {
    "47749e5": {
    name: "Todo"
    },
    "dde4872": {
    name: "Doing"
    },
    "ac32fcc": {
    name: “Shipped"
    }
    }
    columns(
    kanbanState,
    renameColumn("ac32fcc", "Shipped"));
    "ac32fcc": {
    name: “Shipped"
    }
    {
    "47749e5": {
    name: "Todo"
    },
    "dde4872": {
    name: "Doing"
    },
    "ac32fcc": {
    name: "Done"
    }
    }
    "ac32fcc": {
    name: "Done"
    }

    View Slide

  162. // reducers/tasks.js
    export default function tasks(state = {}, { type, payload }) {
    if (type === ADD_TASK_TO_COLUMN) {
    return {
    ...state,
    [v4()]: {
    column: payload.columnId
    }
    };
    }
    return state;
    }
    Step 3: reducers

    View Slide

  163. Step 3:
    combining reducers

    View Slide

  164. // reducers/index.ks
    import columns from "./columns";
    import tasks from "./tasks";
    import users from "./users";
    const rootReducer = combineReducers({
    columns,
    tasks,
    users
    });
    Step 3:
    combining reducers

    View Slide

  165. Step 3:
    combining reducers
    {
    columns: {
    '1': {
    name: 'Todo'
    },
    '2': {
    name: 'Doing'
    },
    '3', {
    name: 'Done'
    }
    },
    tasks: {
    '1': {
    name: 'Task 1',
    column: '1'
    },
    '2': {
    name: 'Task 2',
    column: '2'
    }
    }
    }
    {
    columns: {
    },
    tasks: {
    }

    View Slide

  166. function getColumnsWithTasks(state) {
    const columns = arrayfyObject(state.columns || {});
    const tasks = arrayfyObject(state.tasks || {});
    return columns.map(column => ({
    ...column,
    tasks: tasks.filter(t => t.column === column.id)
    }));
    }
    A more complex selector

    View Slide

  167. getColumnsWithTasks(state);

    View Slide

  168. getColumnsWithTasks(state);
    {
    columns: {
    '1': {
    name: 'Todo'
    },
    '2': {
    name: 'Doing'
    },
    '3', {
    name: 'Done'
    }
    },
    tasks: {
    '1': {
    name: 'Task 1',
    column: '1'
    },
    '2': {
    name: 'Task 2',
    column: '2'
    }
    }
    }
    columns:
    tasks:

    View Slide

  169. getColumnsWithTasks(state);
    [
    {
    name: "todo",
    id: '1',
    tasks: [
    {
    name: "Task 1",
    id: '1'
    }
    ]
    },
    {
    name: "doing",
    id: '2',
    tasks: [
    {
    name: "Task 2",
    id: '2'
    }
    ]
    },
    {
    name: "done",
    id: '3',
    tasks: []
    }
    ];
    {
    columns: {
    '1': {
    name: 'Todo'
    },
    '2': {
    name: 'Doing'
    },
    '3', {
    name: 'Done'
    }
    },
    tasks: {
    '1': {
    name: 'Task 1',
    column: '1'
    },
    '2': {
    name: 'Task 2',
    column: '2'
    }
    }
    }
    columns:
    tasks:
    {
    name: "todo",
    id: '1',
    tasks: [
    {
    name: "Task 1",
    id: '1'
    }
    ]
    }

    View Slide

  170. Presentational
    Components
    Container
    Components
    Presentational and Container Components by Dan Abramov

    View Slide

  171. Presentational
    Components
    Container
    Components
    concern how things look how things work
    Presentational and Container Components by Dan Abramov

    View Slide

  172. Presentational
    Components
    Container
    Components
    concern how things look how things work
    dependencies — actions and store
    Presentational and Container Components by Dan Abramov

    View Slide

  173. Presentational
    Components
    Container
    Components
    concern how things look how things work
    dependencies — actions and store
    business logic — -
    Presentational and Container Components by Dan Abramov

    View Slide

  174. Presentational
    Components
    Container
    Components
    concern how things look how things work
    dependencies — actions and store
    business logic — (redux)
    Presentational and Container Components by Dan Abramov


    visual
    interaction

    View Slide

  175. Presentational Component

    View Slide

  176. export default const Column = ({
    name,
    onAddTask
    }) =>

    {name}

    Add Task


    Presentational Component
    export default const Column = ({
    name,
    onAddTask
    }) =>

    {name}

    Add Task


    View Slide

  177. export default const Column = ({
    name,
    onAddTask
    }) =>

    {name}

    Add Task


    @Component({
    selector: 'column',
    template: `

    {{ name }}

    Add Task

    `
    })
    export class ColumnComponent {
    @Input() name;
    @Output() onAddTask = new EventEmitter();
    }
    Presentational Component
    export default const Column = ({
    name,
    onAddTask
    }) =>

    {name}

    Add Task


    @Component({
    selector: 'column',
    template: `

    {{ name }}

    Add Task

    `
    })
    export class ColumnComponent {
    @Input() name;
    @Output() onAddTask = new EventEmitter();
    }

    View Slide

  178. export default const Column = ({
    name,
    onAddTask
    }) =>

    {name}

    Add Task


    @Component({
    selector: 'column',
    template: `

    {{ name }}

    Add Task

    `
    })
    export class ColumnComponent {
    @Input() name;
    @Output() onAddTask = new EventEmitter();
    }


    {{ name }}

    Add Task



    <br/>export default {<br/>props: ['name', 'onAddTask'],<br/>}<br/>
    Presentational Component
    export default const Column = ({
    name,
    onAddTask
    }) =>

    {name}

    Add Task


    @Component({
    selector: 'column',
    template: `

    {{ name }}

    Add Task

    `
    })
    export class ColumnComponent {
    @Input() name;
    @Output() onAddTask = new EventEmitter();
    }


    {{ name }}

    Add Task



    <br/>export default {<br/>props: ['name', 'onAddTask'],<br/>}<br/>

    View Slide

  179. export default const Column = ({
    name,
    onAddTask
    }) =>

    {name}

    Add Task


    @Component({
    selector: 'column',
    template: `

    {{ name }}

    Add Task

    `
    })
    export class ColumnComponent {
    @Input() name;
    @Output() onAddTask = new EventEmitter();
    }


    {{ name }}

    Add Task



    <br/>export default {<br/>props: ['name', 'onAddTask'],<br/>}<br/>
    Presentational Component: Structure

    View Slide

  180. export default const Column = ({
    name,
    onAddTask
    }) =>

    {name}

    Add Task


    @Component({
    selector: 'column',
    template: `

    {{ name }}

    Add Task

    `
    })
    export class ColumnComponent {
    @Input() name;
    @Output() onAddTask = new EventEmitter();
    }


    {{ name }}

    Add Task



    <br/>export default {<br/>props: ['name', 'onAddTask'],<br/>}<br/>
    Presentational Component: Structure
    export default const Column = ({
    }) =>


    View Slide

  181. export default const Column = ({
    name,
    onAddTask
    }) =>

    {name}

    Add Task


    @Component({
    selector: 'column',
    template: `

    {{ name }}

    Add Task

    `
    })
    export class ColumnComponent {
    @Input() name;
    @Output() onAddTask = new EventEmitter();
    }


    {{ name }}

    Add Task



    <br/>export default {<br/>props: ['name', 'onAddTask'],<br/>}<br/>
    Presentational Component: Structure
    export default const Column = ({
    }) =>


    @Component({
    selector: 'column',
    template: `

    `
    })
    export class ColumnComponent {
    }

    View Slide

  182. export default const Column = ({
    name,
    onAddTask
    }) =>

    {name}

    Add Task


    @Component({
    selector: 'column',
    template: `

    {{ name }}

    Add Task

    `
    })
    export class ColumnComponent {
    @Input() name;
    @Output() onAddTask = new EventEmitter();
    }


    {{ name }}

    Add Task



    <br/>export default {<br/>props: ['name', 'onAddTask'],<br/>}<br/>
    Presentational Component: Structure
    export default const Column = ({
    }) =>


    @Component({
    selector: 'column',
    template: `

    `
    })
    export class ColumnComponent {
    }




    <br/>export default {<br/>}<br/>

    View Slide

  183. export default const Column = ({
    name,
    onAddTask
    }) =>

    {name}

    Add Task


    @Component({
    selector: 'column',
    template: `

    {{ name }}

    Add Task

    `
    })
    export class ColumnComponent {
    @Input() name;
    @Output() onAddTask = new EventEmitter();
    }


    {{ name }}

    Add Task



    <br/>export default {<br/>props: ['name', 'onAddTask'],<br/>}<br/>
    Presentational Component: Structure
    export default const Column = ({
    }) =>


    @Component({
    selector: 'column',
    template: `

    `
    })
    export class ColumnComponent {
    }




    <br/>export default {<br/>}<br/>

    View Slide

  184. export default const Column = ({
    name,
    onAddTask
    }) =>

    {name}

    Add Task


    @Component({
    selector: 'column',
    template: `

    {{ name }}

    Add Task

    `
    })
    export class ColumnComponent {
    @Input() name;
    @Output() onAddTask = new EventEmitter();
    }


    {{ name }}

    Add Task



    <br/>export default {<br/>props: ['name', 'onAddTask'],<br/>}<br/>
    Presentational Component: Inputs/Outputs

    View Slide

  185. export default const Column = ({
    name,
    onAddTask
    }) =>

    {name}

    Add Task


    @Component({
    selector: 'column',
    template: `

    {{ name }}

    Add Task

    `
    })
    export class ColumnComponent {
    @Input() name;
    @Output() onAddTask = new EventEmitter();
    }


    {{ name }}

    Add Task



    <br/>export default {<br/>props: ['name', 'onAddTask'],<br/>}<br/>
    Presentational Component: Inputs/Outputs
    name,
    onAddTask

    View Slide

  186. export default const Column = ({
    name,
    onAddTask
    }) =>

    {name}

    Add Task


    @Component({
    selector: 'column',
    template: `

    {{ name }}

    Add Task

    `
    })
    export class ColumnComponent {
    @Input() name;
    @Output() onAddTask = new EventEmitter();
    }


    {{ name }}

    Add Task



    <br/>export default {<br/>props: ['name', 'onAddTask'],<br/>}<br/>
    Presentational Component: Inputs/Outputs
    name,
    onAddTask
    @Input() name;
    @Output() onAddTask = new EventEmitter();

    View Slide

  187. export default const Column = ({
    name,
    onAddTask
    }) =>

    {name}

    Add Task


    @Component({
    selector: 'column',
    template: `

    {{ name }}

    Add Task

    `
    })
    export class ColumnComponent {
    @Input() name;
    @Output() onAddTask = new EventEmitter();
    }


    {{ name }}

    Add Task



    <br/>export default {<br/>props: ['name', 'onAddTask'],<br/>}<br/>
    Presentational Component: Inputs/Outputs
    name,
    onAddTask
    @Input() name;
    @Output() onAddTask = new EventEmitter();
    props: ['name', 'onAddTask'],

    View Slide

  188. export default const Column = ({
    name,
    onAddTask
    }) =>

    {name}

    Add Task


    @Component({
    selector: 'column',
    template: `

    {{ name }}

    Add Task

    `
    })
    export class ColumnComponent {
    @Input() name;
    @Output() onAddTask = new EventEmitter();
    }


    {{ name }}

    Add Task



    <br/>export default {<br/>props: ['name', 'onAddTask'],<br/>}<br/>
    Presentational Component: Inputs/Outputs
    name,
    onAddTask
    @Input() name;
    @Output() onAddTask = new EventEmitter();
    props: ['name', 'onAddTask'],

    View Slide

  189. export default const Column = ({
    name,
    onAddTask
    }) =>

    {name}

    Add Task


    @Component({
    selector: 'column',
    template: `

    {{ name }}

    Add Task

    `
    })
    export class ColumnComponent {
    @Input() name;
    @Output() onAddTask = new EventEmitter();
    }


    {{ name }}

    Add Task



    <br/>export default {<br/>props: ['name', 'onAddTask'],<br/>}<br/>
    Presentational Component: Binding inputs

    View Slide

  190. export default const Column = ({
    name,
    onAddTask
    }) =>

    {name}

    Add Task


    @Component({
    selector: 'column',
    template: `

    {{ name }}

    Add Task

    `
    })
    export class ColumnComponent {
    @Input() name;
    @Output() onAddTask = new EventEmitter();
    }


    {{ name }}

    Add Task



    <br/>export default {<br/>props: ['name', 'onAddTask'],<br/>}<br/>
    Presentational Component: Binding inputs
    {name}

    View Slide

  191. export default const Column = ({
    name,
    onAddTask
    }) =>

    {name}

    Add Task


    @Component({
    selector: 'column',
    template: `

    {{ name }}

    Add Task

    `
    })
    export class ColumnComponent {
    @Input() name;
    @Output() onAddTask = new EventEmitter();
    }


    {{ name }}

    Add Task



    <br/>export default {<br/>props: ['name', 'onAddTask'],<br/>}<br/>
    Presentational Component: Binding inputs
    {name}
    {{ name }}

    View Slide

  192. export default const Column = ({
    name,
    onAddTask
    }) =>

    {name}

    Add Task


    @Component({
    selector: 'column',
    template: `

    {{ name }}

    Add Task

    `
    })
    export class ColumnComponent {
    @Input() name;
    @Output() onAddTask = new EventEmitter();
    }


    {{ name }}

    Add Task



    <br/>export default {<br/>props: ['name', 'onAddTask'],<br/>}<br/>
    Presentational Component: Binding inputs
    {name}
    {{ name }}
    {{ name }}

    View Slide

  193. export default const Column = ({
    name,
    onAddTask
    }) =>

    {name}

    Add Task


    @Component({
    selector: 'column',
    template: `

    {{ name }}

    Add Task

    `
    })
    export class ColumnComponent {
    @Input() name;
    @Output() onAddTask = new EventEmitter();
    }


    {{ name }}

    Add Task



    <br/>export default {<br/>props: ['name', 'onAddTask'],<br/>}<br/>
    Presentational Component: Binding inputs
    {name}
    {{ name }}
    {{ name }}

    View Slide

  194. export default const Column = ({
    name,
    onAddTask
    }) =>

    {name}

    Add Task


    @Component({
    selector: 'column',
    template: `

    {{ name }}

    Add Task

    `
    })
    export class ColumnComponent {
    @Input() name;
    @Output() onAddTask = new EventEmitter();
    }


    {{ name }}

    Add Task



    <br/>export default {<br/>props: ['name', 'onAddTask'],<br/>}<br/>
    Presentational Component: Emitting outputs
    +
    +

    View Slide

  195. export default const Column = ({
    name,
    onAddTask
    }) =>

    {name}

    Add Task


    @Component({
    selector: 'column',
    template: `

    {{ name }}

    Add Task

    `
    })
    export class ColumnComponent {
    @Input() name;
    @Output() onAddTask = new EventEmitter();
    }


    {{ name }}

    Add Task



    <br/>export default {<br/>props: ['name', 'onAddTask'],<br/>}<br/>
    Presentational Component: Emitting outputs
    onClick={onAddTask}>

    View Slide

  196. export default const Column = ({
    name,
    onAddTask
    }) =>

    {name}

    Add Task


    @Component({
    selector: 'column',
    template: `

    {{ name }}

    Add Task

    `
    })
    export class ColumnComponent {
    @Input() name;
    @Output() onAddTask = new EventEmitter();
    }


    {{ name }}

    Add Task



    <br/>export default {<br/>props: ['name', 'onAddTask'],<br/>}<br/>
    Presentational Component: Emitting outputs
    onClick={onAddTask}>
    (click)="onAddTask.emit()">

    View Slide

  197. export default const Column = ({
    name,
    onAddTask
    }) =>

    {name}

    Add Task


    @Component({
    selector: 'column',
    template: `

    {{ name }}

    Add Task

    `
    })
    export class ColumnComponent {
    @Input() name;
    @Output() onAddTask = new EventEmitter();
    }


    {{ name }}

    Add Task



    <br/>export default {<br/>props: ['name', 'onAddTask'],<br/>}<br/>
    Presentational Component: Emitting outputs
    onClick={onAddTask}>
    (click)="onAddTask.emit()">
    @click=“onAddTask">

    View Slide

  198. export default const Column = ({
    name,
    onAddTask
    }) =>

    {name}

    Add Task


    @Component({
    selector: 'column',
    template: `

    {{ name }}

    Add Task

    `
    })
    export class ColumnComponent {
    @Input() name;
    @Output() onAddTask = new EventEmitter();
    }


    {{ name }}

    Add Task



    <br/>export default {<br/>props: ['name', 'onAddTask'],<br/>}<br/>
    Presentational Component: Emitting outputs
    onClick={onAddTask}>
    (click)="onAddTask.emit()">
    @click=“onAddTask">

    View Slide

  199. Presentational Component


    View Slide

  200. Container Component

    View Slide

  201. Container Component
    import { connect } from "react-redux";
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers";
    const Trello = ({
    columns, renameColumn, addTaskToColumn
    }) => (

    {columns.map(({ id, name, tasks }, index) => (
    key={id}
    name={name}
    onColumnNameChange={
    (name) => renameColumn(id, name)}
    onAddTask={() => addTaskToColumn(id)}
    >
    {tasks.map(({ id: tId, ...rest }) => (

    ))}

    ))}

    );
    export default connect(
    state => ({ columns: getColumnsWithTasks(state) }),
    actions
    )(Trello);
    import { connect } from "react-redux";
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers";
    const Trello = ({
    columns, renameColumn, addTaskToColumn
    }) => (

    {columns.map(({ id, name, tasks }, index) => (
    key={id}
    name={name}
    onColumnNameChange={
    (name) => renameColumn(id, name)}
    onAddTask={() => addTaskToColumn(id)}
    >
    {tasks.map(({ id: tId, ...rest }) => (

    ))}

    ))}

    );
    export default connect(
    state => ({ columns: getColumnsWithTasks(state) }),
    actions
    )(Trello);

    View Slide

  202. Container Component
    import { connect } from "react-redux";
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers";
    const Trello = ({
    columns, renameColumn, addTaskToColumn
    }) => (

    {columns.map(({ id, name, tasks }, index) => (
    key={id}
    name={name}
    onColumnNameChange={
    (name) => renameColumn(id, name)}
    onAddTask={() => addTaskToColumn(id)}
    >
    {tasks.map(({ id: tId, ...rest }) => (

    ))}

    ))}

    );
    export default connect(
    state => ({ columns: getColumnsWithTasks(state) }),
    actions
    )(Trello);
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers/columns";
    @Component({
    selector: "app-board",
    template: `

    *ngFor="let column of columns"
    [name]="column.name"
    (onAddTask)="store.dispatch(
    actions.addTask(column.id)
    )"
    (onColumnNameChange)="store.dispatch(
    actions.renameColumn(column.id, $event.target.name)
    )">
    [name]="t.name">


    `
    })
    export class BoardComponent {
    constructor(store: Store) {
    this.store = store;
    this.actions = actions;
    store.subscribe(state => {
    this.columns = getColumnsWithTasks(state);
    });
    }
    }
    import { connect } from "react-redux";
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers";
    const Trello = ({
    columns, renameColumn, addTaskToColumn
    }) => (

    {columns.map(({ id, name, tasks }, index) => (
    key={id}
    name={name}
    onColumnNameChange={
    (name) => renameColumn(id, name)}
    onAddTask={() => addTaskToColumn(id)}
    >
    {tasks.map(({ id: tId, ...rest }) => (

    ))}

    ))}

    );
    export default connect(
    state => ({ columns: getColumnsWithTasks(state) }),
    actions
    )(Trello);
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers/columns";
    @Component({
    selector: "app-board",
    template: `

    *ngFor="let column of columns"
    [name]="column.name"
    (onAddTask)="store.dispatch(
    actions.addTask(column.id)
    )"
    (onColumnNameChange)="store.dispatch(
    actions.renameColumn(column.id, $event.target.name)
    )">
    [name]="t.name">


    `
    })
    export class BoardComponent {
    constructor(store: Store) {
    this.store = store;
    this.actions = actions;
    store.subscribe(state => {
    this.columns = getColumnsWithTasks(state);
    });
    }
    }

    View Slide

  203. Container Component
    import { connect } from "react-redux";
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers";
    const Trello = ({
    columns, renameColumn, addTaskToColumn
    }) => (

    {columns.map(({ id, name, tasks }, index) => (
    key={id}
    name={name}
    onColumnNameChange={
    (name) => renameColumn(id, name)}
    onAddTask={() => addTaskToColumn(id)}
    >
    {tasks.map(({ id: tId, ...rest }) => (

    ))}

    ))}

    );
    export default connect(
    state => ({ columns: getColumnsWithTasks(state) }),
    actions
    )(Trello);


    v-for="(column, index) in state.columns"
    :key="column.id"
    :name="column.name"
    @onColumnNameChange="(name) => renameColumn(id, name)"
    @onAddTask="() => addTaskToColumn(column.id)">
    v-for="task in column.tasks"
    :key="`task-${task.id}`"
    :name="task.name"/>



    <br/>import * as actions from "../../common/actions";<br/>import { getColumnsWithTasks } from "../../common/reducers";<br/>import { connect } from "../store";<br/>export default {<br/>mixins: [connect(<br/>state => ({ columns: getColumnsWithTasks(state) }).<br/>actions<br/>)]<br/>};<br/>
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers/columns";
    @Component({
    selector: "app-board",
    template: `

    *ngFor="let column of columns"
    [name]="column.name"
    (onAddTask)="store.dispatch(
    actions.addTask(column.id)
    )"
    (onColumnNameChange)="store.dispatch(
    actions.renameColumn(column.id, $event.target.name)
    )">
    [name]="t.name">


    `
    })
    export class BoardComponent {
    constructor(store: Store) {
    this.store = store;
    this.actions = actions;
    store.subscribe(state => {
    this.columns = getColumnsWithTasks(state);
    });
    }
    }
    import { connect } from "react-redux";
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers";
    const Trello = ({
    columns, renameColumn, addTaskToColumn
    }) => (

    {columns.map(({ id, name, tasks }, index) => (
    key={id}
    name={name}
    onColumnNameChange={
    (name) => renameColumn(id, name)}
    onAddTask={() => addTaskToColumn(id)}
    >
    {tasks.map(({ id: tId, ...rest }) => (

    ))}

    ))}

    );
    export default connect(
    state => ({ columns: getColumnsWithTasks(state) }),
    actions
    )(Trello);


    v-for="(column, index) in state.columns"
    :key="column.id"
    :name="column.name"
    @onColumnNameChange="(name) => renameColumn(id, name)"
    @onAddTask="() => addTaskToColumn(column.id)">
    v-for="task in column.tasks"
    :key="`task-${task.id}`"
    :name="task.name"/>



    <br/>import * as actions from "../../common/actions";<br/>import { getColumnsWithTasks } from "../../common/reducers";<br/>import { connect } from "../store";<br/>export default {<br/>mixins: [connect(<br/>state => ({ columns: getColumnsWithTasks(state) }).<br/>actions<br/>)]<br/>};<br/>
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers/columns";
    @Component({
    selector: "app-board",
    template: `

    *ngFor="let column of columns"
    [name]="column.name"
    (onAddTask)="store.dispatch(
    actions.addTask(column.id)
    )"
    (onColumnNameChange)="store.dispatch(
    actions.renameColumn(column.id, $event.target.name)
    )">
    [name]="t.name">


    `
    })
    export class BoardComponent {
    constructor(store: Store) {
    this.store = store;
    this.actions = actions;
    store.subscribe(state => {
    this.columns = getColumnsWithTasks(state);
    });
    }
    }

    View Slide

  204. Container Component: Redux bindings
    import { connect } from "react-redux";
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers";
    const Trello = ({
    columns, renameColumn, addTaskToColumn
    }) => (

    {columns.map(({ id, name, tasks }, index) => (
    key={id}
    name={name}
    onColumnNameChange={
    (name) => renameColumn(id, name)}
    onAddTask={() => addTaskToColumn(id)}
    >
    {tasks.map(({ id: tId, ...rest }) => (

    ))}

    ))}

    );
    export default connect(
    state => ({ columns: getColumnsWithTasks(state) }),
    actions
    )(Trello);


    v-for="(column, index) in state.columns"
    :key="column.id"
    :name="column.name"
    @onColumnNameChange="(name) => renameColumn(id, name)"
    @onAddTask="() => addTaskToColumn(column.id)">
    v-for="task in column.tasks"
    :key="`task-${task.id}`"
    :name="task.name"/>



    <br/>import * as actions from "../../common/actions";<br/>import { getColumnsWithTasks } from "../../common/reducers";<br/>import { connect } from "../store";<br/>export default {<br/>mixins: [connect(<br/>state => ({ columns: getColumnsWithTasks(state) }).<br/>actions<br/>)]<br/>};<br/>
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers/columns";
    @Component({
    selector: "app-board",
    template: `

    *ngFor="let column of columns"
    [name]="column.name"
    (onAddTask)="store.dispatch(
    actions.addTask(column.id)
    )"
    (onColumnNameChange)="store.dispatch(
    actions.renameColumn(column.id, $event.target.name)
    )">
    [name]="t.name">


    `
    })
    export class BoardComponent {
    constructor(store: Store) {
    this.store = store;
    this.actions = actions;
    store.subscribe(state => {
    this.columns = getColumnsWithTasks(state);
    });
    }
    }

    View Slide

  205. Container Component: Redux bindings
    import { connect } from "react-redux";
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers";
    const Trello = ({
    columns, renameColumn, addTaskToColumn
    }) => (

    {columns.map(({ id, name, tasks }, index) => (
    key={id}
    name={name}
    onColumnNameChange={
    (name) => renameColumn(id, name)}
    onAddTask={() => addTaskToColumn(id)}
    >
    {tasks.map(({ id: tId, ...rest }) => (

    ))}

    ))}

    );
    export default connect(
    state => ({ columns: getColumnsWithTasks(state) }),
    actions
    )(Trello);


    v-for="(column, index) in state.columns"
    :key="column.id"
    :name="column.name"
    @onColumnNameChange="(name) => renameColumn(id, name)"
    @onAddTask="() => addTaskToColumn(column.id)">
    v-for="task in column.tasks"
    :key="`task-${task.id}`"
    :name="task.name"/>



    <br/>import * as actions from "../../common/actions";<br/>import { getColumnsWithTasks } from "../../common/reducers";<br/>import { connect } from "../store";<br/>export default {<br/>mixins: [connect(<br/>state => ({ columns: getColumnsWithTasks(state) }).<br/>actions<br/>)]<br/>};<br/>
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers/columns";
    @Component({
    selector: "app-board",
    template: `

    *ngFor="let column of columns"
    [name]="column.name"
    (onAddTask)="store.dispatch(
    actions.addTask(column.id)
    )"
    (onColumnNameChange)="store.dispatch(
    actions.renameColumn(column.id, $event.target.name)
    )">
    [name]="t.name">


    `
    })
    export class BoardComponent {
    constructor(store: Store) {
    this.store = store;
    this.actions = actions;
    store.subscribe(state => {
    this.columns = getColumnsWithTasks(state);
    });
    }
    }
    import { connect } from "react-redux";
    export default connect(
    )( );

    View Slide

  206. Container Component: Redux bindings
    import { connect } from "react-redux";
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers";
    const Trello = ({
    columns, renameColumn, addTaskToColumn
    }) => (

    {columns.map(({ id, name, tasks }, index) => (
    key={id}
    name={name}
    onColumnNameChange={
    (name) => renameColumn(id, name)}
    onAddTask={() => addTaskToColumn(id)}
    >
    {tasks.map(({ id: tId, ...rest }) => (

    ))}

    ))}

    );
    export default connect(
    state => ({ columns: getColumnsWithTasks(state) }),
    actions
    )(Trello);


    v-for="(column, index) in state.columns"
    :key="column.id"
    :name="column.name"
    @onColumnNameChange="(name) => renameColumn(id, name)"
    @onAddTask="() => addTaskToColumn(column.id)">
    v-for="task in column.tasks"
    :key="`task-${task.id}`"
    :name="task.name"/>



    <br/>import * as actions from "../../common/actions";<br/>import { getColumnsWithTasks } from "../../common/reducers";<br/>import { connect } from "../store";<br/>export default {<br/>mixins: [connect(<br/>state => ({ columns: getColumnsWithTasks(state) }).<br/>actions<br/>)]<br/>};<br/>
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers/columns";
    @Component({
    selector: "app-board",
    template: `

    *ngFor="let column of columns"
    [name]="column.name"
    (onAddTask)="store.dispatch(
    actions.addTask(column.id)
    )"
    (onColumnNameChange)="store.dispatch(
    actions.renameColumn(column.id, $event.target.name)
    )">
    [name]="t.name">


    `
    })
    export class BoardComponent {
    constructor(store: Store) {
    this.store = store;
    this.actions = actions;
    store.subscribe(state => {
    this.columns = getColumnsWithTasks(state);
    });
    }
    }
    import { connect } from "react-redux";
    export default connect(
    )( );
    store: Store
    this.store = store;
    this.actions = actions;
    store.subscribe(state => {
    });

    View Slide

  207. Container Component: Redux bindings
    import { connect } from "react-redux";
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers";
    const Trello = ({
    columns, renameColumn, addTaskToColumn
    }) => (

    {columns.map(({ id, name, tasks }, index) => (
    key={id}
    name={name}
    onColumnNameChange={
    (name) => renameColumn(id, name)}
    onAddTask={() => addTaskToColumn(id)}
    >
    {tasks.map(({ id: tId, ...rest }) => (

    ))}

    ))}

    );
    export default connect(
    state => ({ columns: getColumnsWithTasks(state) }),
    actions
    )(Trello);


    v-for="(column, index) in state.columns"
    :key="column.id"
    :name="column.name"
    @onColumnNameChange="(name) => renameColumn(id, name)"
    @onAddTask="() => addTaskToColumn(column.id)">
    v-for="task in column.tasks"
    :key="`task-${task.id}`"
    :name="task.name"/>



    <br/>import * as actions from "../../common/actions";<br/>import { getColumnsWithTasks } from "../../common/reducers";<br/>import { connect } from "../store";<br/>export default {<br/>mixins: [connect(<br/>state => ({ columns: getColumnsWithTasks(state) }).<br/>actions<br/>)]<br/>};<br/>
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers/columns";
    @Component({
    selector: "app-board",
    template: `

    *ngFor="let column of columns"
    [name]="column.name"
    (onAddTask)="store.dispatch(
    actions.addTask(column.id)
    )"
    (onColumnNameChange)="store.dispatch(
    actions.renameColumn(column.id, $event.target.name)
    )">
    [name]="t.name">


    `
    })
    export class BoardComponent {
    constructor(store: Store) {
    this.store = store;
    this.actions = actions;
    store.subscribe(state => {
    this.columns = getColumnsWithTasks(state);
    });
    }
    }
    import { connect } from "react-redux";
    export default connect(
    )( );
    import { connect } from "../store";
    export default {
    mixins: [connect(
    )]
    };
    store: Store
    this.store = store;
    this.actions = actions;
    store.subscribe(state => {
    });

    View Slide

  208. Container Component: Redux bindings
    import { connect } from "react-redux";
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers";
    const Trello = ({
    columns, renameColumn, addTaskToColumn
    }) => (

    {columns.map(({ id, name, tasks }, index) => (
    key={id}
    name={name}
    onColumnNameChange={
    (name) => renameColumn(id, name)}
    onAddTask={() => addTaskToColumn(id)}
    >
    {tasks.map(({ id: tId, ...rest }) => (

    ))}

    ))}

    );
    export default connect(
    state => ({ columns: getColumnsWithTasks(state) }),
    actions
    )(Trello);


    v-for="(column, index) in state.columns"
    :key="column.id"
    :name="column.name"
    @onColumnNameChange="(name) => renameColumn(id, name)"
    @onAddTask="() => addTaskToColumn(column.id)">
    v-for="task in column.tasks"
    :key="`task-${task.id}`"
    :name="task.name"/>



    <br/>import * as actions from "../../common/actions";<br/>import { getColumnsWithTasks } from "../../common/reducers";<br/>import { connect } from "../store";<br/>export default {<br/>mixins: [connect(<br/>state => ({ columns: getColumnsWithTasks(state) }).<br/>actions<br/>)]<br/>};<br/>
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers/columns";
    @Component({
    selector: "app-board",
    template: `

    *ngFor="let column of columns"
    [name]="column.name"
    (onAddTask)="store.dispatch(
    actions.addTask(column.id)
    )"
    (onColumnNameChange)="store.dispatch(
    actions.renameColumn(column.id, $event.target.name)
    )">
    [name]="t.name">


    `
    })
    export class BoardComponent {
    constructor(store: Store) {
    this.store = store;
    this.actions = actions;
    store.subscribe(state => {
    this.columns = getColumnsWithTasks(state);
    });
    }
    }
    import { connect } from "react-redux";
    export default connect(
    )( );
    import { connect } from "../store";
    export default {
    mixins: [connect(
    )]
    };
    store: Store
    this.store = store;
    this.actions = actions;
    store.subscribe(state => {
    });

    View Slide

  209. Container Component: Behaviour
    import { connect } from "react-redux";
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers";
    const Trello = ({
    columns, renameColumn, addTaskToColumn
    }) => (

    {columns.map(({ id, name, tasks }, index) => (
    key={id}
    name={name}
    onColumnNameChange={
    (name) => renameColumn(id, name)}
    onAddTask={() => addTaskToColumn(id)}
    >
    {tasks.map(({ id: tId, ...rest }) => (

    ))}

    ))}

    );
    export default connect(
    state => ({ columns: getColumnsWithTasks(state) }),
    actions
    )(Trello);


    v-for="(column, index) in state.columns"
    :key="column.id"
    :name="column.name"
    @onColumnNameChange="(name) => renameColumn(id, name)"
    @onAddTask="() => addTaskToColumn(column.id)">
    v-for="task in column.tasks"
    :key="`task-${task.id}`"
    :name="task.name"/>



    <br/>import * as actions from "../../common/actions";<br/>import { getColumnsWithTasks } from "../../common/reducers";<br/>import { connect } from "../store";<br/>export default {<br/>mixins: [connect(<br/>state => ({ columns: getColumnsWithTasks(state) }).<br/>actions<br/>)]<br/>};<br/>
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers/columns";
    @Component({
    selector: "app-board",
    template: `

    *ngFor="let column of columns"
    [name]="column.name"
    (onAddTask)="store.dispatch(
    actions.addTask(column.id)
    )"
    (onColumnNameChange)="store.dispatch(
    actions.renameColumn(column.id, $event.target.name)
    )">
    [name]="t.name">


    `
    })
    export class BoardComponent {
    constructor(store: Store) {
    this.store = store;
    this.actions = actions;
    store.subscribe(state => {
    this.columns = getColumnsWithTasks(state);
    });
    }
    }

    View Slide

  210. Container Component: Behaviour
    import { connect } from "react-redux";
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers";
    const Trello = ({
    columns, renameColumn, addTaskToColumn
    }) => (

    {columns.map(({ id, name, tasks }, index) => (
    key={id}
    name={name}
    onColumnNameChange={
    (name) => renameColumn(id, name)}
    onAddTask={() => addTaskToColumn(id)}
    >
    {tasks.map(({ id: tId, ...rest }) => (

    ))}

    ))}

    );
    export default connect(
    state => ({ columns: getColumnsWithTasks(state) }),
    actions
    )(Trello);


    v-for="(column, index) in state.columns"
    :key="column.id"
    :name="column.name"
    @onColumnNameChange="(name) => renameColumn(id, name)"
    @onAddTask="() => addTaskToColumn(column.id)">
    v-for="task in column.tasks"
    :key="`task-${task.id}`"
    :name="task.name"/>



    <br/>import * as actions from "../../common/actions";<br/>import { getColumnsWithTasks } from "../../common/reducers";<br/>import { connect } from "../store";<br/>export default {<br/>mixins: [connect(<br/>state => ({ columns: getColumnsWithTasks(state) }).<br/>actions<br/>)]<br/>};<br/>
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers/columns";
    @Component({
    selector: "app-board",
    template: `

    *ngFor="let column of columns"
    [name]="column.name"
    (onAddTask)="store.dispatch(
    actions.addTask(column.id)
    )"
    (onColumnNameChange)="store.dispatch(
    actions.renameColumn(column.id, $event.target.name)
    )">
    [name]="t.name">


    `
    })
    export class BoardComponent {
    constructor(store: Store) {
    this.store = store;
    this.actions = actions;
    store.subscribe(state => {
    this.columns = getColumnsWithTasks(state);
    });
    }
    }
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers";
    onColumnNameChange={
    (name) => renameColumn(id, name)}
    onAddTask={() => addTaskToColumn(id)}
    state => ({ columns: getColumnsWithTasks(state) }),
    actions

    View Slide

  211. Container Component: Behaviour
    import { connect } from "react-redux";
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers";
    const Trello = ({
    columns, renameColumn, addTaskToColumn
    }) => (

    {columns.map(({ id, name, tasks }, index) => (
    key={id}
    name={name}
    onColumnNameChange={
    (name) => renameColumn(id, name)}
    onAddTask={() => addTaskToColumn(id)}
    >
    {tasks.map(({ id: tId, ...rest }) => (

    ))}

    ))}

    );
    export default connect(
    state => ({ columns: getColumnsWithTasks(state) }),
    actions
    )(Trello);


    v-for="(column, index) in state.columns"
    :key="column.id"
    :name="column.name"
    @onColumnNameChange="(name) => renameColumn(id, name)"
    @onAddTask="() => addTaskToColumn(column.id)">
    v-for="task in column.tasks"
    :key="`task-${task.id}`"
    :name="task.name"/>



    <br/>import * as actions from "../../common/actions";<br/>import { getColumnsWithTasks } from "../../common/reducers";<br/>import { connect } from "../store";<br/>export default {<br/>mixins: [connect(<br/>state => ({ columns: getColumnsWithTasks(state) }).<br/>actions<br/>)]<br/>};<br/>
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers/columns";
    @Component({
    selector: "app-board",
    template: `

    *ngFor="let column of columns"
    [name]="column.name"
    (onAddTask)="store.dispatch(
    actions.addTask(column.id)
    )"
    (onColumnNameChange)="store.dispatch(
    actions.renameColumn(column.id, $event.target.name)
    )">
    [name]="t.name">


    `
    })
    export class BoardComponent {
    constructor(store: Store) {
    this.store = store;
    this.actions = actions;
    store.subscribe(state => {
    this.columns = getColumnsWithTasks(state);
    });
    }
    }
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers";
    onColumnNameChange={
    (name) => renameColumn(id, name)}
    onAddTask={() => addTaskToColumn(id)}
    state => ({ columns: getColumnsWithTasks(state) }),
    actions
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers/columns";
    (onAddTask)="store.dispatch(
    actions.addTask(column.id)
    )"
    (onColumnNameChange)="store.dispatch(
    actions.renameColumn(column.id, $event.target.name)
    )">
    this.store = store;
    this.actions = actions;
    store.subscribe(state => {
    this.columns = getColumnsWithTasks(state);
    });

    View Slide

  212. Container Component: Behaviour
    import { connect } from "react-redux";
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers";
    const Trello = ({
    columns, renameColumn, addTaskToColumn
    }) => (

    {columns.map(({ id, name, tasks }, index) => (
    key={id}
    name={name}
    onColumnNameChange={
    (name) => renameColumn(id, name)}
    onAddTask={() => addTaskToColumn(id)}
    >
    {tasks.map(({ id: tId, ...rest }) => (

    ))}

    ))}

    );
    export default connect(
    state => ({ columns: getColumnsWithTasks(state) }),
    actions
    )(Trello);


    v-for="(column, index) in state.columns"
    :key="column.id"
    :name="column.name"
    @onColumnNameChange="(name) => renameColumn(id, name)"
    @onAddTask="() => addTaskToColumn(column.id)">
    v-for="task in column.tasks"
    :key="`task-${task.id}`"
    :name="task.name"/>



    <br/>import * as actions from "../../common/actions";<br/>import { getColumnsWithTasks } from "../../common/reducers";<br/>import { connect } from "../store";<br/>export default {<br/>mixins: [connect(<br/>state => ({ columns: getColumnsWithTasks(state) }).<br/>actions<br/>)]<br/>};<br/>
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers/columns";
    @Component({
    selector: "app-board",
    template: `

    *ngFor="let column of columns"
    [name]="column.name"
    (onAddTask)="store.dispatch(
    actions.addTask(column.id)
    )"
    (onColumnNameChange)="store.dispatch(
    actions.renameColumn(column.id, $event.target.name)
    )">
    [name]="t.name">


    `
    })
    export class BoardComponent {
    constructor(store: Store) {
    this.store = store;
    this.actions = actions;
    store.subscribe(state => {
    this.columns = getColumnsWithTasks(state);
    });
    }
    }
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers";
    onColumnNameChange={
    (name) => renameColumn(id, name)}
    onAddTask={() => addTaskToColumn(id)}
    state => ({ columns: getColumnsWithTasks(state) }),
    actions
    @onColumnNameChange="(name) => renameColumn(id, name)"
    @onAddTask="() => addTaskToColumn(column.id)">
    import * as actions from "../../common/actions";
    import { getColumnsWithTasks } from "../../common/reducers";
    state => ({ columns: getColumnsWithTasks(state) }).
    actions
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers/columns";
    (onAddTask)="store.dispatch(
    actions.addTask(column.id)
    )"
    (onColumnNameChange)="store.dispatch(
    actions.renameColumn(column.id, $event.target.name)
    )">
    this.store = store;
    this.actions = actions;
    store.subscribe(state => {
    this.columns = getColumnsWithTasks(state);
    });

    View Slide

  213. Container Component: Behaviour
    import { connect } from "react-redux";
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers";
    const Trello = ({
    columns, renameColumn, addTaskToColumn
    }) => (

    {columns.map(({ id, name, tasks }, index) => (
    key={id}
    name={name}
    onColumnNameChange={
    (name) => renameColumn(id, name)}
    onAddTask={() => addTaskToColumn(id)}
    >
    {tasks.map(({ id: tId, ...rest }) => (

    ))}

    ))}

    );
    export default connect(
    state => ({ columns: getColumnsWithTasks(state) }),
    actions
    )(Trello);


    v-for="(column, index) in state.columns"
    :key="column.id"
    :name="column.name"
    @onColumnNameChange="(name) => renameColumn(id, name)"
    @onAddTask="() => addTaskToColumn(column.id)">
    v-for="task in column.tasks"
    :key="`task-${task.id}`"
    :name="task.name"/>



    <br/>import * as actions from "../../common/actions";<br/>import { getColumnsWithTasks } from "../../common/reducers";<br/>import { connect } from "../store";<br/>export default {<br/>mixins: [connect(<br/>state => ({ columns: getColumnsWithTasks(state) }).<br/>actions<br/>)]<br/>};<br/>
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers/columns";
    @Component({
    selector: "app-board",
    template: `

    *ngFor="let column of columns"
    [name]="column.name"
    (onAddTask)="store.dispatch(
    actions.addTask(column.id)
    )"
    (onColumnNameChange)="store.dispatch(
    actions.renameColumn(column.id, $event.target.name)
    )">
    [name]="t.name">


    `
    })
    export class BoardComponent {
    constructor(store: Store) {
    this.store = store;
    this.actions = actions;
    store.subscribe(state => {
    this.columns = getColumnsWithTasks(state);
    });
    }
    }
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers";
    onColumnNameChange={
    (name) => renameColumn(id, name)}
    onAddTask={() => addTaskToColumn(id)}
    state => ({ columns: getColumnsWithTasks(state) }),
    actions
    @onColumnNameChange="(name) => renameColumn(id, name)"
    @onAddTask="() => addTaskToColumn(column.id)">
    import * as actions from "../../common/actions";
    import { getColumnsWithTasks } from "../../common/reducers";
    state => ({ columns: getColumnsWithTasks(state) }).
    actions
    import * as actions from "../actions";
    import { getColumnsWithTasks } from "../reducers/columns";
    (onAddTask)="store.dispatch(
    actions.addTask(column.id)
    )"
    (onColumnNameChange)="store.dispatch(
    actions.renameColumn(column.id, $event.target.name)
    )">
    this.store = store;
    this.actions = actions;
    store.subscribe(state => {
    this.columns = getColumnsWithTasks(state);
    });

    View Slide

  214. Container Component


    View Slide

  215. View Slide

  216. ∴ The web will keep changing.
    and so will our tools

    View Slide

  217. ∴ The web will keep changing.
    and so will our tools
    ∴ Framework wars are only a developers
    problem.
    nobody else cares

    View Slide

  218. ∴ The web will keep changing.
    and so will our tools
    ∴ Framework wars are only a developers
    problem.
    nobody else cares
    ∴ Framework-agnostic web applications
    helps us make peace both.
    redux is a good ally in this journey

    View Slide

  219. Thank you.

    View Slide