redux for reactjs programmers

137
Redux for ReactJs Programmers by Dr. David Rodenas

Upload: david-rodenas

Post on 11-Apr-2017

62 views

Category:

Software


1 download

TRANSCRIPT

Redux for ReactJs Programmers

by Dr. David Rodenas

state

@drpicox

MVC

@drpicox

MV

C

@drpicox

http://heim.ifi.uio.no/~trygver/themes/mvc/mvc-index.html

@drpicox

Route: /users/123/edit or Route: /floors/3/rooms/5 or Route: /admin

Users: {id: 123, name: 'bob', ...} {id: 124, name: 'alice', ...}

Thermostats: {floor: 3, room: 5, max: 26, min: 18, ...}, {floor: 5, room: 2, max: 24, min: 22, ...},

Preferences: dateFormat: 'dd-mm-yyyy' heater: 'on' cooler: 'off'

6

@drpicox

Route: /users/123/edit

Users: {id: 123, name: 'bob', ...} {id: 124, name: 'alice', ...}

Thermostats: {floor: 3, room: 5, max: 26, min: 18, ...}, {floor: 5, room: 2, max: 24, min: 22, ...},

Preferences: dateFormat: 'dd-mm-yyyy' heater: 'on' cooler: 'off'

7

https://martinfowler.com/eaaDev/uiArchs.html

three kind of states:

· record state

· session state

· screen state{

@drpicox

MV

C

@drpicox

MV

C

widgets

handlers

@drpicox

MV

Chandlers

widgets

React

@drpicox

MV

C

widgets

React

handlers

@drpicox

MV

C

widgets

React

+ state

handlers

@drpicox

MVCMVPMVVCMVVM

MV*

Two key benefits of MV* are:

• "attach multiple views to a model to provide different presentations"

• "change the way a view responds to user input without changing its visual presentation"

@drpicox https://xkcd.com/927/

@drpicox

“MVC works pretty well for small applications…

but it doesn’t make room for new features.”

“Flux is a single direction data flow, that avoids all the arrows

going on all directions what makes really

hard to understand the system.”

https://youtu.be/nYkdrAPrdcw?t=10m20s

@drpicox https://xkcd.com/927/

@drpicox

https://www.youtube.com/watch?v=-jwQ3sGoiXg

@drpicox

https://www.youtube.com/watch?v=-jwQ3sGoiXg

@drpicox

https://www.youtube.com/watch?v=-jwQ3sGoiXg

origin

@drpicox

https://www.youtube.com/watch?v=xsSnOQynTHs

@drpicox

Inspired in Flux• Like Flux:

• Single direction data-flow

• Has actions

• Actions are dispatched

• Has stores concept

• Views subscribes to stores

22

@drpicox

Inspired in Flux• Unlike Flux:

• No dispatcher

• Single store

> Single source of truth

• State is computed with reducers

> State is read-only

> Changes are pure functions

• State is injected to views

23

@drpicox

Flux

24

https://facebook.github.io/flux/

1 * *

*

@drpicox

Flux// dispatcher.js const AppDispatcher = new Dispatcher(); export default AppDispatcher;

// AppDispatcher.register(function(action) { ... }); // AppDispatcher.dispatch(action);

25

@drpicox

Flux// stores/todos.js const _todos = [];

const TodoStore = {...EventEmitter.prototype, { getAll() { return _todos; }, });

AppDispatcher.register(function(action) { ... TodoStore.emit('change'); });

export default TodoStore;

26

@drpicox

Flux// components/TodoList.js class TodoList extends React.Component { constructor() { ... } componentDidMount() { TodoStore.addListener('change', this.updateState); } componentWillUnmount() { TodoStore.removeListener('change', this.updateState); } updateState = () => { this.setState({todos: TodoStore.getAll()}); } render() { ... } }

27

@drpicox

Flux// components/AddTodo.js class AddTodo extends React.Component { constructor() { ... } handleClick = () => { AppDispatcher.dispatch({ type: 'ADD_TODO', text: this.state.value, }); } render() { ... } }

28

@drpicox

const _todos = [];

const TodoStore = {...EventEmitter.prototype, { getAll() { return _todos; }, });

AppDispatcher.register(function(action) { switch (action.type) { case ActionTypes.TODO_CREATE: let text = action.text.trim(); _todos.push(text); TodoStore.emit('change'); } });

export default TodoStore;29

@drpicox

const _todos = [];

const TodoStore = {...EventEmitter.prototype, { getAll() { return _todos; }, });

AppDispatcher.register(function(action) { switch (action.type) { case ActionTypes.TODO_CREATE: let text = action.text.trim(); _todos.push(text); TodoStore.emit('change'); } });

export default TodoStore;30

@drpicox

const _todos = [];

const TodoStore = {...EventEmitter.prototype, { getAll() { return _todos; }, });

export function handle(action) { switch (action.type) { case ActionTypes.TODO_CREATE: let text = action.text.trim(); _todos.push(text); TodoStore.emit('change'); } });

export default TodoStore;31

@drpicox

const _todos = [];

const TodoStore = {...EventEmitter.prototype, { getAll() { return _todos; }, });

export function handle(action) { switch (action.type) { case ActionTypes.TODO_CREATE: let text = action.text.trim(); _todos.push(text); TodoStore.emit('change'); } });

export default TodoStore;32

@drpicox

const _todos = [];

const TodoStore = {...EventEmitter.prototype, { getAll() { return _todos; }, });

export function handle(action) { switch (action.type) { case ActionTypes.TODO_CREATE: let text = action.text.trim(); _todos = [..._todos, text]; TodoStore.emit('change'); } });

export default TodoStore;33

@drpicox

const _todos = [];

const TodoStore = {...EventEmitter.prototype, { getAll() { return _todos; }, });

export function handle(action) { switch (action.type) { case ActionTypes.TODO_CREATE: let text = action.text.trim(); _todos = [..._todos, text]; TodoStore.emit('change'); } });

export default TodoStore;34

@drpicox

const _todos = [];

const TodoStore = new EventEmitter();

export function getState() { return _todos; }

export function handle(action) { switch (action.type) { case ActionTypes.TODO_CREATE: let text = action.text.trim(); _todos = [..._todos, text]; TodoStore.emit('change'); } });

export default TodoStore;35

@drpicox

const _todos = [];

const TodoStore = new EventEmitter();

export function getState() { return _todos; }

export function handle(action) { switch (action.type) { case ActionTypes.TODO_CREATE: let text = action.text.trim(); _todos = [..._todos, text]; TodoStore.emit('change'); } });

export default TodoStore;36

@drpicox

const _todos = [];

export function getState() { return _todos; }

export function handle(action) { switch (action.type) { case ActionTypes.TODO_CREATE: let text = action.text.trim(); _todos = [..._todos, text];

} });

37

@drpicox

const _todos = [];

export function getState() { return _todos; }

export function handle(action) { switch (action.type) { case ActionTypes.TODO_CREATE: let text = action.text.trim(); _todos = [..._todos, text];

} });

38

@drpicox

const _todos = [];

export function handle(action) { switch (action.type) { case ActionTypes.TODO_CREATE: let text = action.text.trim(); _todos = [..._todos, text];

} });

39

@drpicox

const _todos = [];

export function handle(action) { switch (action.type) { case ActionTypes.TODO_CREATE: let text = action.text.trim(); _todos = [..._todos, text];

} });

40

@drpicox

export function handle(state, action) { switch (action.type) { case ActionTypes.TODO_CREATE: let text = action.text.trim(); return [..._todos, text]; default: return state; } });

41

@drpicox

export function handle(state = [], action) { switch (action.type) { case ActionTypes.TODO_CREATE: let text = action.text.trim(); return [..._todos, text]; default: return state; } });

42

Redux 101

Actions

@drpicox

Actions// actions are identified by type const ADD_TODO = 'ADD_TODO'

// actions are always a JSON equivalent object const exampleOfAction = { type: ADD_TODO, text: 'Learn Redux', };

// we use function creators for actions export function addTodo(text) { return {type: ADD_TODO, text}; };

45http://redux.js.org/docs/basics/Actions.html#source-code

State

@drpicox

State// state is one single object { visibilityFilter: 'SHOW_ALL', todos: [ { id: 123, text: 'Learn Redux', completed: false, }, ], }

// state should be normalized (use pk/fk)

47

Reducers

@drpicox

Handle Action// execute an action is like: (previousState, action) => newState

49

@drpicox

Handle Actionconst reducer = (previousState, action) => newState

50

@drpicox

Handle Actionconst reducer = (previousState, action) => newState

const actions = [ addTodo('buy redux book'), addTodo('read redux book'), toggleTodo(3), setVisibilityFilter(SHOW_ACTIVE), ... ];

51

@drpicox

Handle Actionconst reducer = (previousState, action) => newState

const actions = [ addTodo('buy redux book'), addTodo('read redux book'), toggleTodo(3), setVisibilityFilter(SHOW_ACTIVE), ... ];

const finalAppState = actions.reduce(reducer);

52

@drpicox

Reducerconst reducer = (state, action) => { switch (action.type) { case SET_VISIBILITY_FILTER: return { ...state, visibilityFilter: action.filter }; } };

53

@drpicox

Reducerconst reducer = (state, action) => { switch (action.type) { case SET_VISIBILITY_FILTER: return { ...state, visibilityFilter: action.filter }; default: return state; } };

54

@drpicox

Reducerconst reducer = (state, action) => { switch (action.type) { case SET_VISIBILITY_FILTER: return { ...state, visibilityFilter: action.filter }; case ADD_TODO: return { ...state, todos: [ ...state.todos, { text: action.text, completed: false } ]}; default: return state; } };

55

@drpicox

Reducerconst reducer = (state, action) => { switch (action.type) { case SET_VISIBILITY_FILTER: ... case ADD_TODO: ... case TOGGLE_TODO: return { ...state, todos: state.todos.map((todo, index) => { if (index === action.index) { return {...todo, completed: !todo.completed}; } return todo; } }; default: ... } };

56

@drpicox

Initial Stateconst reducer = (previousState, action) => newState

const actions = [ addTodo('buy redux book'), addTodo('read redux book'), toggleTodo(3), setVisibilityFilter(SHOW_ACTIVE), ... ];

const finalAppState = actions.reduce(reducer);

57

Something is missing: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce#Syntax

@drpicox

Initial Stateconst reducer = (previousState, action) => newState

const actions = [ ... ];

const initialState = { visibilityFilter: SHOW_ALL, todos: [], };

const finalAppState = actions.reduce(reducer, initialState);

58

@drpicox

Initial Stateconst reducer = (previousState, action) => newState

const actions = [ ... ];

const initialState = { visibilityFilter: SHOW_ALL, todos: [], };

const finalAppState = actions.reduce(reducer, undefined);

59

@drpicox

Reducersconst initialState = { ... }; const reducer = (state, action) => { if (state === undefined) { state = initialState; }

switch (action.type) { case SET_VISIBILITY_FILTER: ... case ADD_TODO: ... case TOGGLE_TODO: ... default: ... } };

60

@drpicox

Reducersconst initialState = { ... }; const reducer = (state = initialState, action) => { switch (action.type) { case SET_VISIBILITY_FILTER: ... case ADD_TODO: ... case TOGGLE_TODO: ... default: ... } };

61

@drpicox

Splitting Reducersconst initialState = { ... }; const reducer = (state = initialState, action) => { switch (action.type) { case SET_VISIBILITY_FILTER: ... case ADD_TODO: ... case TOGGLE_TODO: ... default: ... } };

62

@drpicox

Splitting Reducersconst todos = (state, action) => { switch (action.type) { case ADD_TODO: ... case TOGGLE_TODO: ... default: ... } };

const visibilityFilter = (state, action) => { switch (action.type) { case SET_VISIBILITY_FILTER: ... default: ... } };

63

@drpicox

Splitting Reducersconst initialState = { ... }; const reducer = (state = initialState, action) => { state = visibilityFilter(state, action); state = todos(state, action); return state; };

64

@drpicox

Splitting Reducerconst initialState = { ... }; const reducer = (state = initialState, action) => { return { visibilityFilter: visibilityFilter( state.visibilityFilter, action), todos: todos(state.todos, action) }; };

65

@drpicox

Splitting Reducers

const reducer = (state = {}, action) => { return { visibilityFilter: visibilityFilter( state.visibilityFilter, action), todos: todos(state.todos, action) }; };

66

@drpicox

Splitting Reducersconst visibilityFilter = (state = SHOW_ALL, action) => { switch (action.type) { case SET_VISIBILITY_FILTER: return action.filter; default: return state; } };

67

@drpicox

Splitting Reducersconst todos = (state = [], action) => { switch (action.type) { case ADD_TODO: return [...state, { text: action.text, completed: false }; case TOGGLE_TODO: return state.map((todo, index) => { if (index === action.index) { return {...todo, completed: !todo.completed}; } return todo; } default: return state; } }; 68

@drpicox

Combine Reducersconst todoApp = (state = {}, action) => { return { visibilityFilter: visibilityFilter( state.visibilityFilter, action), todos: todos(state.todos, action) }; };

69

@drpicox

Combine Reducersimport { combineReducers } from 'redux';

const todoApp = combineReducers({ visibilityFilter, todos });

70http://redux.js.org/docs/basics/Reducers.html#source-code

Store

@drpicox

Storeimport { createStore } from 'redux';

const store = createStore(reducer,/*initialState*/);

// store.getState(): state // store.dispatch(action) // store.subscribe(listener): unsubscribeFn

72http://redux.js.org/docs/basics/Store.html

@drpicox

Dispathing actions// Do you remember? const actions = [ addTodo('buy redux book'), addTodo('read redux book'), toggleTodo(3), setVisibilityFilter(SHOW_ACTIVE), ... ];

73

@drpicox

Dispathing actions// Using the store: store.dispatch(addTodo('buy redux book')); store.dispatch(addTodo('read redux book')); store.dispatch(toggleTodo(3)); store.dispatch(setVisibilityFilter(SHOW_ACTIVE));

74

Redux + React

@drpicox

Bindings$ npm install --save react-redux

76http://redux.js.org/docs/basics/UsageWithReact.html

@drpicox

Presentational vs Container

77

Presentational Container

Purpose How to render things (html + css)

How things work (fetch data, updates, ...)

Aware of Redux No Yes

To read data Read data from props Subscribe to Redux state

To change data Invoke callbacks from props Dispatch Redux actions

Written by hand usually by react-redux

@drpicox

Application• Stores, reducers and actions are pure Redux

• Presentational components are pure React

• You may design them first

• Container components are the glue

• Design which data needs and which actions performs

• Decide which presentational components wrap

78

@drpicox

Presentational Componentfunction Toggle(props) { return ( <span onClick={props.onToggle}> {props.toggle ? 'on' : 'off'} </span> ); }

79

@drpicox

Container Componentconst mapStateToProps = (state) => ({ toggle: state, });

const mapDispatchToProps = (dispatch, ownProps) => ({ onToggle: () => dispatch(toggle()), });

const ToggleContainer = ReactRedux.connect( mapStateToProps, mapDispatchToProps )(Toggle);

80

@drpicox

Container Componentconst mapStateToProps = (state) => ({ toggle: state, });

const mapDispatchToProps = (dispatch, ownProps) => ({ onToggle: () => dispatch(toggle()), });

const ToggleContainer = ReactRedux.connect( mapStateToProps, mapDispatchToProps )(Toggle);

81

@drpicox

Providing the Storeimport { Provider } from 'react-redux';

ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') );

82

Async

Async I

@drpicox

Asynchronousconst PING = 'PING'; const PONG = 'PONG';

const ping = () => ({ type: PING }); const pong = () => ({ type: PONG });

function pingPong() {

setTimeout(() => dispatch(pong()), 1000);

return ping(); }

85

@drpicox

Asynchronousconst PING = 'PING'; const PONG = 'PONG';

const ping = () => ({ type: PING }); const pong = () => ({ type: PONG });

function pingPong() {

setTimeout(() => dispatch(pong()), 1000);

return ping(); }

86

Middleware

@drpicox

Middleware• What if we want log all actions?

• Log previous state

• Log dispatched action

• Log resulting state

88

@drpicox

Middlewarelet action = addTodo('Use Redux')

console.log('dispatching', action) store.dispatch(action) console.log('next state', store.getState())

89

@drpicox

Middlewarefunction dispatchAndLog(store, action) { console.log('dispatching', action) store.dispatch(action) console.log('next state', store.getState()) }

dispatchAndLog(store, addTodo('Use Redux'))

90

@drpicox

Middleware• What if we also want save change states?

• What if we also want report crashes?

• What if we also want to time travel?

91

@drpicox

Middlewarefunction patchStoreToAddLoggin(store) { next = store.dispatch;

store.dispatch = function dispatchAndLog(action) { console.log('dispatching', action); let result = next(action); console.log('next state', store.getState()); return result; } }

92

@drpicox

MiddlewarepatchStoreToAddLoggin(store); patchStoreToSaveState(store); patchStoreToReportCrashes(store); patchStoreToTimeTravel(store);

store.dispatch(addTodo('Use Redux'))

93

@drpicox

Middlewarefunction patchStoreToAddLoggin(store) { next = store.dispatch;

store.dispatch = function dispatchAndLog(action) { console.log('dispatching', action); let result = next(action); console.log('next state', store.getState()); return result; } }

94

@drpicox

Middlewarefunction logger(store) { return function wrapDispatchToAddLogging(next) { return function dispatchAndLog(action) { console.log('dispatching', action); let result = next(action); console.log('next state', store.getState()); return result; } } }

95

@drpicox

Middlewareconst logger = (store) => (next) => (action) { console.log('dispatching', action); let result = next(action); console.log('next state', store.getState()); return result; }

96

@drpicox

Middlewareconst next4 = store.dispatch; const next3 = logger(store)(next4); const next2 = stateSaver(store)(next3); const next1 = crashReporter(store)(next2); const next0 = timeTraveler(store)(next1);

next0(addTodo('Use Redux'))

97

@drpicox

Middlewareconst middlewares = [ logger, // 3 stateSaver, // 2 crashReporter, // 1 timeTraveler, // 0 ]; const next = middlewares.reduce( (next, middleware) => middleware(store)(next) , store.dispatch);

next(addTodo('Use Redux'));

98

@drpicox

Middlewareconst middlewares = [ timeTraveler, // 0 crashReporter, // 1 stateSaver, // 2 logger, // 3 ]; const next = middlewares.reduceRight( (next, middleware) => middleware(store)(next) , store.dispatch);

next(addTodo('Use Redux'));

99

@drpicox

Enhancerconst applyMiddleware = (...middlewares) => { return (createStore) => (reducer, initialState) => { const store = createStore(reducer, initialState); const dispatch = middlewares.reduceRight( (next, middleware) => middleware(store)(next) , store.dispatch); return { ...store, dispatch }; } }

const store = applyMiddleware(logger)(createStore)(reducer, {});

100

@drpicox

EnhancercreateStore(reducer, initialState, enhancer) ... return enhancer(createStore)(reducer, initialState);

const store = applyMiddleware(logger)(createStore)(reducer, {}); // === const store = createStore( reducer, {}, applyMiddleware(logger) );

101

@drpicox

Enhancerconst applyMiddleware = (...middlewares) => { return (createStore) => (reducer, initialState, enhancer) => { const store = createStore(reducer, initialState, enhancer); const dispatch = [...middlewares].reverse() .reduce((next, middleware) => { return middleware(store)(next); }, store.dispatch); return { ...store, dispatch }; } }

102

@drpicox

Enhancerenhancer = (next: StoreCreator) => StoreCreator

const store = applyMiddleware(logger)( DevTools.instrument()(createStore)( reducer, initialState ) );

103

@drpicox

Enhancerenhancer = (next: StoreCreator) => StoreCreator

const store = createStore( reducer, initialState, compose( applyMiddleware(logger), DevTools.instrument() ) );

104

@drpicox

Composeconst greet = (x) => `Hello, ${x}.`; const emote = (x) => `${x} :)`;

const compose = function(f, g) { return function(x) { return f(g(x)); } }

let happyGreeting = compose(greet, emote); // happyGreeting("Bob") -> Hello, Bob :).

105

@drpicox

Recapconst middleware = store => next => action => { // do something former let result = next(action); // do something latter return result; };

const store = createStore( reducer, initialState, applyMiddleware(middleware) );

106

Async II

@drpicox

Asynchronousconst PING = 'PING'; const PONG = 'PONG';

const ping = () => ({ type: PING }); const pong = () => ({ type: PONG });

function pingPong() {

setTimeout(() => dispatch(pong()), 1000);

return ping(); }

108

@drpicox

Asynchronousconst PING = 'PING'; const PONG = 'PONG';

const ping = () => ({ type: PING }); const pong = () => ({ type: PONG });

function pingPong() { return (dispatch) => { setTimeout(() => dispatch(pong()), 1000);

return ping(); }; }

109

@drpicox

Recapconst middleware = store => next => action => {

return next(action); };

110

@drpicox

"Inject" dispatchconst middleware = store => next => action => { if (typeof action === 'function') { return action( store.dispatch, store.getState ); } return next(action); };

111

@drpicox

Redux-thunkconst thunkMiddleware = store => next => action => { if (typeof action === 'function') { return action( store.dispatch, store.getState ); } return next(action); };

112

@drpicox

Asynchronousconst PING = 'PING'; const PONG = 'PONG';

const ping = () => ({ type: PING }); const pong = () => ({ type: PONG });

function pingPong() { return (dispatch) => { setTimeout(() => dispatch(pong()), 1000);

dispatch(ping()); }; }

113

@drpicox

Asynchronousconst PING = 'PING'; const PONG = 'PONG';

const ping = () => ({ type: PING }); const pong = () => ({ type: PONG });

function pingPong() { return (dispatch) => { dispatch(ping());

setTimeout(() => dispatch(pong()), 1000); }; }

114

Remote I/O

@drpicox

Actions{ type: 'FETCH_POSTS' }

{ type: 'FETCH_POSTS', status: 'error', error: 'Ops' }

{ type: 'FETCH_POSTS', status: 'success', response: { ... } }

116

@drpicox

Actions{ type: 'FETCH_POSTS_REQUEST' }

{ type: 'FETCH_POSTS_FAILURE', error: 'Ops' }

{ type: 'FETCH_POSTS_SUCCESS', response: { ... } }

117

@drpicox

Actionsconst requestPosts = () => ({ type: 'FETCH_POSTS_REQUEST' });

const requestPostsFailure = (error) => ({ type: 'FETCH_POSTS_FAILURE', error });

const receivePosts = (response) => ({ type: 'FETCH_POSTS_SUCCESS', response });

118

@drpicox

Actionsconst fetchPostsRequest = () => ({ type: 'FETCH_POSTS_REQUEST' });

const fetchPostsFailure = (error) => ({ type: 'FETCH_POSTS_FAILURE', error });

const fetchPostsSuccess = (response) => ({ type: 'FETCH_POSTS_SUCCESS', response });

119

@drpicox

Async Actionconst fetchPosts => () => (dispatch) { dispatch(fetchPostsRequest()); apiGet('/posts') .then((response) => dispatch(fetchPostsSuccess(response))) .catch((error) => dispatch(fetchPostsFailure(error)));

}

120

Managing State

Normalized Data

http://redux.js.org/docs/recipes/reducers/NormalizingStateShape.html http://redux.js.org/docs/recipes/reducers/UpdatingNormalizedData.html

@drpicox

Data Example{ "id": "123", "author": { "id": "1", "name": "Paul" }, "title": "My awesome blog post", "comments": [ { "id": "324", "commenter": { "id": "2", "name": "Nicole" } } ] }

123

@drpicox

Unnormalized Data{ "id": "123", "author": { "id": "1", "name": "Paul" }, "title": "My awesome blog post", "comments": [ { "id": "324", "commenter": { "id": "2", "name": "Nicole" } } ] }

124

@drpicox

Normalized Example"posts": { "123": { "id": "123", "author": "1", "title": "My awesome blog post" "comments": [ "324" ] } }, "comments": { "324": { "id": "324", "commenter": "2" } }, "users": { "1": { "id": "1", "name": "Paul", }, "2": { "id": "2", "name": "Nicole", } } 125

@drpicox

Normalized Stateconst state = { simpleData: ..., entities: { posts: { ... }, comments: { ... }, users: { ... }, }, ui: { selectedComments: [ ... ] } };

126

@drpicox

Normalized Reducers// reducers/entities/comments.js const comments = (state, action) => { ... }; // reducers/entities/posts.js const posts = (state, action) => { ... }; // reducers/entities/users.js const users = (state, action) => { ... }; // reducers/entities/index.js const entities = combineReducers({comments,posts,users}); // reducers/ui/selectedComments.js const ui = selectedComments = (state, action) => { ... }; // reducers/ui/index.js const ui = combineReducers({selectedComments}); // reducers/simpleData.js const simpleData = (state, action) => { ... }; // reducers/index.js const app = combineReducers({simpleData, entities, ui}); 127

@drpicox

Normalized Updatesfunction addComment(postId, commentText) { const commentId = generateId('comment');

return { type: "ADD_COMMENT", postId, commentId, commentText, }; };

128

@drpicox

Normalized Updatesconst comments = (state = {}, action) => { ... case ADD_COMMENT: return { ...state, [action.commentId]: { id: action.commentId, text: action.commentText, } }; ... };

129

@drpicox

Normalized Updatesconst posts = (state = {}, action) => { ... case ADD_COMMENT: const post = state[action.postId]; return { ...state, [post.id]: { ...post, comments: [...post.comments, action.commentId], } }; ... };

130

@drpicox

Normalized State Alt.const state = { simpleData: ..., entities: { posts: { byIds: {...}, allIds: [...] }, comments: { byIds: {...}, allIds: [...] }, users: { byIds: {...}, allIds: [...] }, }, ui: { selectedComments: [ ... ] } };

131

Computed Data

http://redux.js.org/docs/recipes/ComputingDerivedData.html

@drpicox

Selectors• State should be single source of truth

• Normalized data

• No duplicated data

• Computed data can be obtained from state

• Compute under demand

• Memoize results

133

@drpicox

Using reselect$ npm install --save reselect

134

@drpicox

Create Selectorimport { createSelector } from 'reselect';

const getVisibilityFilter = (state) => state.visibilityFilter; const getTodos = (state) => state.todos;

export const getVisibleTodos = createSelector( [ getVisibilityFilter, getTodos ], ( visibilityFilter, todos ) => { switch (visibilityFilter) { case 'SHOW_ALL': return todos case 'SHOW_COMPLETED': return todos.filter(t => t.completed) case 'SHOW_ACTIVE': return todos.filter(t => !t.completed) } } );

135

@drpicox

Selectors of Selectorsconst getKeyword = (state) => state.keyword

const getVisibleTodosFilteredByKeyword = createSelector( [ getVisibleTodos, getKeyword ], ( visibleTodos, keyword ) => { return visibleTodos.filter( todo => todo.text.indexOf(keyword) > -1 ); ) )

136

@drpicox

Selectors and Containersconst mapStateToProps = (state) => ({ todos: getVisibleTodos(state), });

const mapDispatchToProps = (dispatch) => ({ onTodoClick: (id) => { dispatch(toggleTodo(id)) }, });

const VisibleTodoList = connect( mapStateToProps, mapDispatchToProps )(TodoList);

137