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

  2. Agnostic (adjective)

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

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

    b: compatible with many types of platforms or operating systems. (adjective)
  5. Framework-Agnostic Web Apps

  6. Framework-Agnostic Web Apps Applications that are designed in such a

    way that make them not tied to a particular framework.
  7. Why is this important?

  8. let’s talk about THE WEB.

  9. 05 06 07 08 09 10 11 12 13 14

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

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

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

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

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

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

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

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

    15 ® React Native
  18. Javascript FATIGUE

  19. None
  20. None
  21. None
  22. None
  23. None
  24. None
  25. 11 12 13 14 15 16 17 npm modules over

    time [Breaking News] This is not going to slow down.
  26. This is not necessarily bad.

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

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

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

    used to it. Code that embraces change. Agnostic Code.
  30. Framework-Agnostic Web Apps

  31. Framework-Agnostic Web Apps Allow us to easily switch to a

    better fitting framework without the need for a rewrite.
  32. Why is this important?

  33. the web moves faster than you Why is this important?

    (and that’s ok)
  34. How developers judge frameworks?

  35. How developers judge frameworks?

  36. How everybody-else judges frameworks?

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

  38. Users don’t give a DAMN.

  39. 2.7B £ ANGULAR 1

  40. VUE $6B

  41. REACT $8.8B

  42. Focus more on how to build great products.

  43. Views how it looks

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

  45. Views how it looks App Logic what happens if I

    click here Routing what pages do I have
  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
  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
  48. the web moves faster than you Why is this important?

    (and that’s ok)
  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)
  50. How do I even FAWA? Nuff said. shamefully trying to

    create an acronym.
  51. REDUX *increasingly loud drum roll*

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

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

    docs Your framework-agnostic best friend. — Me
  54. REDUX APPS run in different environments (Good because the web

    moves fast)
  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.
  56. Store Action Reducer Component

  57. Store Action Reducer Component Framework-Agnostic Your framework Probably replaced every

    6 months Build to last
  58. STORE ACTIONS REDUCERS

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

    Redux Store
  60. store.getState(); Holds the whole state tree of your application. Redux

    Store { todos: [ { name: 'Attend Frontend Conf' }, { name: 'Introduce Redux Store' }, ], }
  61. Everything is state.

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

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

    Tweets Users Lists Cards Timeline Settings Notifications …
  64. store.subscribe(listener); Handles state change listeners. Redux Store

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

    Store
  66. STORE ACTIONS REDUCERS ✅

  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);
  68. Redux Action Creators function renameTodo(index, name) { } return {

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

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

    type: "RENAME_TODO", payload: { index, name } }; ACTION
  71. Redux Action Creators store.dispatch( renameTodo(0, 'Naming todos is hard.’) );

  72. STORE ACTIONS REDUCERS ✅ ✅

  73. Redux Reducers Actions describe that something happened. Reducers specify how

    the state changes in response. (previous, action) => newState
  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
  75. Redux Reducers counterReducer(0, { type: ‘INCREMENT' }); // 1

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

    { type: ‘INCREMENT_BY', payload: 10 }); // 15
  77. STORE ACTIONS REDUCERS ✅ ✅ ✅

  78. Write a simple redux application and use in React, Vue

    and Angular 2 without code changes. CHALLENGE #1
  79. Store Action Reducer Component Redux Your framework

  80. ADD Buy Groceries Go to Gym x

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

    state
  82. ADD Buy Groceries Go to Gym x [ { name:

    'Buy Groceries', isCompleted: false }, { name: 'Go To Gym', isCompleted: true } ] Step 1: design state
  83. Step 2: actions and action creators

  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
  85. Step 3: reducer

  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
  87. ADD

  88. ADD Step 4: create store

  89. import reducer from './reducers'; const store = createStore(reducer); store.subscribe(() =>

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

    { const todos = store.getState() // [] render(todos); }); ADD Step 4: create store render(todos);
  91. ADD Buy Groceries

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

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

  94. ADD Buy Groceries store.subscribe(() => { const todos = store.getState()

    // [ // { name: 'Buy groceries', isCompleted: false } // ] render(todos); });
  95. Go to Gym ADD Buy Groceries store.dispatch( addTodo('Go to gym’)

    );
  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); });
  97. ADD Buy Groceries Go to Gym x store.dispatch( toggleTodo(1) );

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

    store.dispatch)
  99. Let’s integrate it with <insert framework>

  100. None
  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( <Provider store={store}> <TodoApp /> </Provider>, document.getElementById('root') )
  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( <Provider store={store}> <TodoApp /> </Provider>, 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 { }
  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( <Provider store={store}> <TodoApp /> </Provider>, 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), });
  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( <Provider store={store}> <TodoApp /> </Provider>, 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
  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( <Provider store={store}> <TodoApp /> </Provider>, 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') )
  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( <Provider store={store}> <TodoApp /> </Provider>, 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 { }
  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( <Provider store={store}> <TodoApp /> </Provider>, 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( ), });
  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( <Provider store={store}> <TodoApp /> </Provider>, 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
  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( <Provider store={store}> <TodoApp /> </Provider>, 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
  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( <Provider store={store}> <TodoApp /> </Provider>, 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
  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( <Provider store={store}> <TodoApp /> </Provider>, 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
  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( <Provider store={store}> <TodoApp /> </Provider>, 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
  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( <Provider store={store}> <TodoApp /> </Provider>, 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 ✅
  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( <Provider store={store}> <TodoApp /> </Provider>, 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
  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( <Provider store={store}> <TodoApp /> </Provider>, 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' <Provider store={store}> </Provider>, Binding Redux to Framework
  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( <Provider store={store}> <TodoApp /> </Provider>, 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' <Provider store={store}> </Provider>, import { StoreModule } from '@ngrx/store'; StoreModule.provideStore(reducer) Binding Redux to Framework
  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( <Provider store={store}> <TodoApp /> </Provider>, 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' <Provider store={store}> </Provider>, import { StoreModule } from '@ngrx/store'; StoreModule.provideStore(reducer) Vue.use({ install: () => Object.defineProperty( Vue.proprotype, '$store', { get() { return store; }, }), }); Binding Redux to Framework
  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( <Provider store={store}> <TodoApp /> </Provider>, 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' <Provider store={store}> </Provider>, import { StoreModule } from '@ngrx/store'; StoreModule.provideStore(reducer) Vue.use({ install: () => Object.defineProperty( Vue.proprotype, '$store', { get() { return store; }, }), }); Binding Redux to Framework ⛔
  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( <Provider store={store}> <TodoApp /> </Provider>, 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
  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( <Provider store={store}> <TodoApp /> </Provider>, 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’; <TodoApp /> Top Level Component
  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( <Provider store={store}> <TodoApp /> </Provider>, 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’; <TodoApp /> import TodoApp from ‘./components/TodoApp'; TodoApp Top Level Component
  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( <Provider store={store}> <TodoApp /> </Provider>, 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’; <TodoApp /> import TodoApp from ‘./components/TodoApp'; TodoApp import TodoApp from './components/TodoApp'; TodoApp Top Level Component
  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( <Provider store={store}> <TodoApp /> </Provider>, 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’; <TodoApp /> import TodoApp from ‘./components/TodoApp'; TodoApp import TodoApp from './components/TodoApp'; TodoApp Top Level Component
  124. Bootstrap ✅ ⛔ ⛔

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

  126. None
  127. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> 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 }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> export default connect(state => state, actions)( TodoApp );
  128. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> 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: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ 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 }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> 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: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } }
  129. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> 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: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> import React, { Component } from 'react'; import { connect } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> 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: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script>
  130. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> 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: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> Component Definition
  131. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> 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: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> import React, { Component } from 'react'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> </div> export default ( TodoApp ); Component Definition
  132. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> 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: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> import React, { Component } from 'react'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> </div> export default ( TodoApp ); import { Component } from '@angular/core'; @Component({ selector: 'app-board', template: ` <div> </div>` }) export class BoardComponent { constructor( ){ } } Component Definition
  133. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> 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: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> import React, { Component } from 'react'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> </div> export default ( TodoApp ); import { Component } from '@angular/core'; @Component({ selector: 'app-board', template: ` <div> </div>` }) export class BoardComponent { constructor( ){ } } <template> <div> </div> </template> <script> export default { }; </script> Component Definition
  134. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> 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: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> import React, { Component } from 'react'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> </div> export default ( TodoApp ); import { Component } from '@angular/core'; @Component({ selector: 'app-board', template: ` <div> </div>` }) export class BoardComponent { constructor( ){ } } <template> <div> </div> </template> <script> export default { }; </script> Component Definition ⛔
  135. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> 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: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> Binding Redux to Component
  136. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> 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: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> import { connect } from 'react-redux'; import * as actions from '../actions'; export default connect(state => state, actions)( ); Binding Redux to Component
  137. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> 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: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> 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<any> this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } Binding Redux to Component
  138. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> 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: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> 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<any> 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
  139. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> 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: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> 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<any> 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 ⛔
  140. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> 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: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> Component “Template”
  141. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> 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: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> Component “Template”
  142. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> 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: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` Component “Template”
  143. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> 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: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> Component “Template”
  144. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> 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: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> Component “Template”
  145. Agnostic Components ⛔

  146. We can do BETTER.

  147. Write a trello clone redux application and use in React,

    Vue and Angular 2 without code changes. CHALLENGE #2
  148. None
  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
  150. { "47749e5": { name: "Todo" }, "dde4872": { name: "Doing"

    }, "ac32fcc": { name: "Done" } } Normalized State
  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
  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
  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
  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" };
  155. Step 2: actions // actions/columns.js export const RENAME_COLUMUN = "RENAME_COLUMUN";

    export const renameColumn = (id, name) => ({ type: RENAME_COLUMUN, payload: { id, name } });
  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
  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; }
  158. columns( kanbanState, renameColumn("ac32fcc", "Shipped"));

  159. { "47749e5": { name: "Todo" }, "dde4872": { name: "Doing"

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

    }, "ac32fcc": { name: "Done" } } columns( kanbanState, renameColumn("ac32fcc", "Shipped")); "ac32fcc": { name: "Done" }
  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" }
  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
  163. Step 3: combining reducers

  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
  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: { }
  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
  167. getColumnsWithTasks(state);

  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:
  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' } ] }
  170. Presentational Components Container Components Presentational and Container Components by Dan

    Abramov
  171. Presentational Components Container Components concern how things look how things

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

    work dependencies — actions and store Presentational and Container Components by Dan Abramov
  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
  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
  175. Presentational Component

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

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> Presentational Component export default const Column = ({ name, onAddTask }) => <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div>
  177. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } Presentational Component export default const Column = ({ name, onAddTask }) => <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); }
  178. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component export default const Column = ({ name, onAddTask }) => <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script>
  179. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Structure
  180. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Structure export default const Column = ({ }) => <div> </div>
  181. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Structure export default const Column = ({ }) => <div> </div> @Component({ selector: 'column', template: ` <div> </div>` }) export class ColumnComponent { }
  182. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Structure export default const Column = ({ }) => <div> </div> @Component({ selector: 'column', template: ` <div> </div>` }) export class ColumnComponent { } <template> <div> </div> </template> <script> export default { } </script>
  183. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Structure export default const Column = ({ }) => <div> </div> @Component({ selector: 'column', template: ` <div> </div>` }) export class ColumnComponent { } <template> <div> </div> </template> <script> export default { } </script>
  184. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Inputs/Outputs
  185. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Inputs/Outputs name, onAddTask
  186. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Inputs/Outputs name, onAddTask @Input() name; @Output() onAddTask = new EventEmitter();
  187. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Inputs/Outputs name, onAddTask @Input() name; @Output() onAddTask = new EventEmitter(); props: ['name', 'onAddTask'],
  188. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Inputs/Outputs name, onAddTask @Input() name; @Output() onAddTask = new EventEmitter(); props: ['name', 'onAddTask'], ✅
  189. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Binding inputs
  190. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Binding inputs <span>{name}</span>
  191. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Binding inputs <span>{name}</span> <span>{{ name }}</span>
  192. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Binding inputs <span>{name}</span> <span>{{ name }}</span> <span>{{ name }}</span>
  193. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Binding inputs <span>{name}</span> <span>{{ name }}</span> <span>{{ name }}</span> ✅
  194. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Emitting outputs + +
  195. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Emitting outputs onClick={onAddTask}>
  196. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Emitting outputs onClick={onAddTask}> (click)="onAddTask.emit()">
  197. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Emitting outputs onClick={onAddTask}> (click)="onAddTask.emit()"> @click=“onAddTask">
  198. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Emitting outputs onClick={onAddTask}> (click)="onAddTask.emit()"> @click=“onAddTask">
  199. Presentational Component ✅ ✅

  200. Container Component

  201. Container Component import { connect } from "react-redux"; import *

    as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; const Trello = ({ columns, renameColumn, addTaskToColumn }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); 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 }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); export default connect( state => ({ columns: getColumnsWithTasks(state) }), actions )(Trello);
  202. Container Component import { connect } from "react-redux"; import *

    as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; const Trello = ({ columns, renameColumn, addTaskToColumn }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); export default connect( state => ({ columns: getColumnsWithTasks(state) }), actions )(Trello); import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers/columns"; @Component({ selector: "app-board", template: ` <board> <column *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) )"> <task *ngFor="let t of column.tasks" [name]="t.name"></task> </column> </board> ` }) export class BoardComponent { constructor(store: Store<any>) { 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 }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); export default connect( state => ({ columns: getColumnsWithTasks(state) }), actions )(Trello); import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers/columns"; @Component({ selector: "app-board", template: ` <board> <column *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) )"> <task *ngFor="let t of column.tasks" [name]="t.name"></task> </column> </board> ` }) export class BoardComponent { constructor(store: Store<any>) { this.store = store; this.actions = actions; store.subscribe(state => { this.columns = getColumnsWithTasks(state); }); } }
  203. Container Component import { connect } from "react-redux"; import *

    as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; const Trello = ({ columns, renameColumn, addTaskToColumn }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); export default connect( state => ({ columns: getColumnsWithTasks(state) }), actions )(Trello); <template> <board> <column v-for="(column, index) in state.columns" :key="column.id" :name="column.name" @onColumnNameChange="(name) => renameColumn(id, name)" @onAddTask="() => addTaskToColumn(column.id)"> <task v-for="task in column.tasks" :key="`task-${task.id}`" :name="task.name"/> </column> </board> </template> <script> import * as actions from "../../common/actions"; import { getColumnsWithTasks } from "../../common/reducers"; import { connect } from "../store"; export default { mixins: [connect( state => ({ columns: getColumnsWithTasks(state) }). actions )] }; </script> import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers/columns"; @Component({ selector: "app-board", template: ` <board> <column *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) )"> <task *ngFor="let t of column.tasks" [name]="t.name"></task> </column> </board> ` }) export class BoardComponent { constructor(store: Store<any>) { 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 }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); export default connect( state => ({ columns: getColumnsWithTasks(state) }), actions )(Trello); <template> <board> <column v-for="(column, index) in state.columns" :key="column.id" :name="column.name" @onColumnNameChange="(name) => renameColumn(id, name)" @onAddTask="() => addTaskToColumn(column.id)"> <task v-for="task in column.tasks" :key="`task-${task.id}`" :name="task.name"/> </column> </board> </template> <script> import * as actions from "../../common/actions"; import { getColumnsWithTasks } from "../../common/reducers"; import { connect } from "../store"; export default { mixins: [connect( state => ({ columns: getColumnsWithTasks(state) }). actions )] }; </script> import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers/columns"; @Component({ selector: "app-board", template: ` <board> <column *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) )"> <task *ngFor="let t of column.tasks" [name]="t.name"></task> </column> </board> ` }) export class BoardComponent { constructor(store: Store<any>) { this.store = store; this.actions = actions; store.subscribe(state => { this.columns = getColumnsWithTasks(state); }); } }
  204. Container Component: Redux bindings import { connect } from "react-redux";

    import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; const Trello = ({ columns, renameColumn, addTaskToColumn }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); export default connect( state => ({ columns: getColumnsWithTasks(state) }), actions )(Trello); <template> <board> <column v-for="(column, index) in state.columns" :key="column.id" :name="column.name" @onColumnNameChange="(name) => renameColumn(id, name)" @onAddTask="() => addTaskToColumn(column.id)"> <task v-for="task in column.tasks" :key="`task-${task.id}`" :name="task.name"/> </column> </board> </template> <script> import * as actions from "../../common/actions"; import { getColumnsWithTasks } from "../../common/reducers"; import { connect } from "../store"; export default { mixins: [connect( state => ({ columns: getColumnsWithTasks(state) }). actions )] }; </script> import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers/columns"; @Component({ selector: "app-board", template: ` <board> <column *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) )"> <task *ngFor="let t of column.tasks" [name]="t.name"></task> </column> </board> ` }) export class BoardComponent { constructor(store: Store<any>) { this.store = store; this.actions = actions; store.subscribe(state => { this.columns = getColumnsWithTasks(state); }); } }
  205. Container Component: Redux bindings import { connect } from "react-redux";

    import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; const Trello = ({ columns, renameColumn, addTaskToColumn }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); export default connect( state => ({ columns: getColumnsWithTasks(state) }), actions )(Trello); <template> <board> <column v-for="(column, index) in state.columns" :key="column.id" :name="column.name" @onColumnNameChange="(name) => renameColumn(id, name)" @onAddTask="() => addTaskToColumn(column.id)"> <task v-for="task in column.tasks" :key="`task-${task.id}`" :name="task.name"/> </column> </board> </template> <script> import * as actions from "../../common/actions"; import { getColumnsWithTasks } from "../../common/reducers"; import { connect } from "../store"; export default { mixins: [connect( state => ({ columns: getColumnsWithTasks(state) }). actions )] }; </script> import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers/columns"; @Component({ selector: "app-board", template: ` <board> <column *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) )"> <task *ngFor="let t of column.tasks" [name]="t.name"></task> </column> </board> ` }) export class BoardComponent { constructor(store: Store<any>) { this.store = store; this.actions = actions; store.subscribe(state => { this.columns = getColumnsWithTasks(state); }); } } import { connect } from "react-redux"; export default connect( )( );
  206. Container Component: Redux bindings import { connect } from "react-redux";

    import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; const Trello = ({ columns, renameColumn, addTaskToColumn }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); export default connect( state => ({ columns: getColumnsWithTasks(state) }), actions )(Trello); <template> <board> <column v-for="(column, index) in state.columns" :key="column.id" :name="column.name" @onColumnNameChange="(name) => renameColumn(id, name)" @onAddTask="() => addTaskToColumn(column.id)"> <task v-for="task in column.tasks" :key="`task-${task.id}`" :name="task.name"/> </column> </board> </template> <script> import * as actions from "../../common/actions"; import { getColumnsWithTasks } from "../../common/reducers"; import { connect } from "../store"; export default { mixins: [connect( state => ({ columns: getColumnsWithTasks(state) }). actions )] }; </script> import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers/columns"; @Component({ selector: "app-board", template: ` <board> <column *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) )"> <task *ngFor="let t of column.tasks" [name]="t.name"></task> </column> </board> ` }) export class BoardComponent { constructor(store: Store<any>) { this.store = store; this.actions = actions; store.subscribe(state => { this.columns = getColumnsWithTasks(state); }); } } import { connect } from "react-redux"; export default connect( )( ); store: Store<any> this.store = store; this.actions = actions; store.subscribe(state => { });
  207. Container Component: Redux bindings import { connect } from "react-redux";

    import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; const Trello = ({ columns, renameColumn, addTaskToColumn }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); export default connect( state => ({ columns: getColumnsWithTasks(state) }), actions )(Trello); <template> <board> <column v-for="(column, index) in state.columns" :key="column.id" :name="column.name" @onColumnNameChange="(name) => renameColumn(id, name)" @onAddTask="() => addTaskToColumn(column.id)"> <task v-for="task in column.tasks" :key="`task-${task.id}`" :name="task.name"/> </column> </board> </template> <script> import * as actions from "../../common/actions"; import { getColumnsWithTasks } from "../../common/reducers"; import { connect } from "../store"; export default { mixins: [connect( state => ({ columns: getColumnsWithTasks(state) }). actions )] }; </script> import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers/columns"; @Component({ selector: "app-board", template: ` <board> <column *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) )"> <task *ngFor="let t of column.tasks" [name]="t.name"></task> </column> </board> ` }) export class BoardComponent { constructor(store: Store<any>) { 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<any> this.store = store; this.actions = actions; store.subscribe(state => { });
  208. Container Component: Redux bindings import { connect } from "react-redux";

    import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; const Trello = ({ columns, renameColumn, addTaskToColumn }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); export default connect( state => ({ columns: getColumnsWithTasks(state) }), actions )(Trello); <template> <board> <column v-for="(column, index) in state.columns" :key="column.id" :name="column.name" @onColumnNameChange="(name) => renameColumn(id, name)" @onAddTask="() => addTaskToColumn(column.id)"> <task v-for="task in column.tasks" :key="`task-${task.id}`" :name="task.name"/> </column> </board> </template> <script> import * as actions from "../../common/actions"; import { getColumnsWithTasks } from "../../common/reducers"; import { connect } from "../store"; export default { mixins: [connect( state => ({ columns: getColumnsWithTasks(state) }). actions )] }; </script> import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers/columns"; @Component({ selector: "app-board", template: ` <board> <column *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) )"> <task *ngFor="let t of column.tasks" [name]="t.name"></task> </column> </board> ` }) export class BoardComponent { constructor(store: Store<any>) { 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<any> this.store = store; this.actions = actions; store.subscribe(state => { }); ⛔
  209. Container Component: Behaviour import { connect } from "react-redux"; import

    * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; const Trello = ({ columns, renameColumn, addTaskToColumn }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); export default connect( state => ({ columns: getColumnsWithTasks(state) }), actions )(Trello); <template> <board> <column v-for="(column, index) in state.columns" :key="column.id" :name="column.name" @onColumnNameChange="(name) => renameColumn(id, name)" @onAddTask="() => addTaskToColumn(column.id)"> <task v-for="task in column.tasks" :key="`task-${task.id}`" :name="task.name"/> </column> </board> </template> <script> import * as actions from "../../common/actions"; import { getColumnsWithTasks } from "../../common/reducers"; import { connect } from "../store"; export default { mixins: [connect( state => ({ columns: getColumnsWithTasks(state) }). actions )] }; </script> import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers/columns"; @Component({ selector: "app-board", template: ` <board> <column *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) )"> <task *ngFor="let t of column.tasks" [name]="t.name"></task> </column> </board> ` }) export class BoardComponent { constructor(store: Store<any>) { this.store = store; this.actions = actions; store.subscribe(state => { this.columns = getColumnsWithTasks(state); }); } }
  210. Container Component: Behaviour import { connect } from "react-redux"; import

    * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; const Trello = ({ columns, renameColumn, addTaskToColumn }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); export default connect( state => ({ columns: getColumnsWithTasks(state) }), actions )(Trello); <template> <board> <column v-for="(column, index) in state.columns" :key="column.id" :name="column.name" @onColumnNameChange="(name) => renameColumn(id, name)" @onAddTask="() => addTaskToColumn(column.id)"> <task v-for="task in column.tasks" :key="`task-${task.id}`" :name="task.name"/> </column> </board> </template> <script> import * as actions from "../../common/actions"; import { getColumnsWithTasks } from "../../common/reducers"; import { connect } from "../store"; export default { mixins: [connect( state => ({ columns: getColumnsWithTasks(state) }). actions )] }; </script> import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers/columns"; @Component({ selector: "app-board", template: ` <board> <column *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) )"> <task *ngFor="let t of column.tasks" [name]="t.name"></task> </column> </board> ` }) export class BoardComponent { constructor(store: Store<any>) { 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
  211. Container Component: Behaviour import { connect } from "react-redux"; import

    * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; const Trello = ({ columns, renameColumn, addTaskToColumn }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); export default connect( state => ({ columns: getColumnsWithTasks(state) }), actions )(Trello); <template> <board> <column v-for="(column, index) in state.columns" :key="column.id" :name="column.name" @onColumnNameChange="(name) => renameColumn(id, name)" @onAddTask="() => addTaskToColumn(column.id)"> <task v-for="task in column.tasks" :key="`task-${task.id}`" :name="task.name"/> </column> </board> </template> <script> import * as actions from "../../common/actions"; import { getColumnsWithTasks } from "../../common/reducers"; import { connect } from "../store"; export default { mixins: [connect( state => ({ columns: getColumnsWithTasks(state) }). actions )] }; </script> import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers/columns"; @Component({ selector: "app-board", template: ` <board> <column *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) )"> <task *ngFor="let t of column.tasks" [name]="t.name"></task> </column> </board> ` }) export class BoardComponent { constructor(store: Store<any>) { 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); });
  212. Container Component: Behaviour import { connect } from "react-redux"; import

    * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; const Trello = ({ columns, renameColumn, addTaskToColumn }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); export default connect( state => ({ columns: getColumnsWithTasks(state) }), actions )(Trello); <template> <board> <column v-for="(column, index) in state.columns" :key="column.id" :name="column.name" @onColumnNameChange="(name) => renameColumn(id, name)" @onAddTask="() => addTaskToColumn(column.id)"> <task v-for="task in column.tasks" :key="`task-${task.id}`" :name="task.name"/> </column> </board> </template> <script> import * as actions from "../../common/actions"; import { getColumnsWithTasks } from "../../common/reducers"; import { connect } from "../store"; export default { mixins: [connect( state => ({ columns: getColumnsWithTasks(state) }). actions )] }; </script> import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers/columns"; @Component({ selector: "app-board", template: ` <board> <column *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) )"> <task *ngFor="let t of column.tasks" [name]="t.name"></task> </column> </board> ` }) export class BoardComponent { constructor(store: Store<any>) { 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); });
  213. Container Component: Behaviour import { connect } from "react-redux"; import

    * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; const Trello = ({ columns, renameColumn, addTaskToColumn }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); export default connect( state => ({ columns: getColumnsWithTasks(state) }), actions )(Trello); <template> <board> <column v-for="(column, index) in state.columns" :key="column.id" :name="column.name" @onColumnNameChange="(name) => renameColumn(id, name)" @onAddTask="() => addTaskToColumn(column.id)"> <task v-for="task in column.tasks" :key="`task-${task.id}`" :name="task.name"/> </column> </board> </template> <script> import * as actions from "../../common/actions"; import { getColumnsWithTasks } from "../../common/reducers"; import { connect } from "../store"; export default { mixins: [connect( state => ({ columns: getColumnsWithTasks(state) }). actions )] }; </script> import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers/columns"; @Component({ selector: "app-board", template: ` <board> <column *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) )"> <task *ngFor="let t of column.tasks" [name]="t.name"></task> </column> </board> ` }) export class BoardComponent { constructor(store: Store<any>) { 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); });
  214. Container Component ⛔

  215. None
  216. ∴ The web will keep changing. and so will our

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

    tools ∴ Framework wars are only a developers problem. nobody else cares
  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
  219. Thank you.