redux for reactjs programmers
TRANSCRIPT
@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
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
“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
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// 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
@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
@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
@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
@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
@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
@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
@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
@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
"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
@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
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
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