SlideShare a Scribd company logo
Redux for ReactJs
Programmers
by Dr. David Rodenas
State
MVC
M
V
C
http://heim.ifi.uio.no/~trygver/themes/mvc/mvc-index.html
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
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
{
M
V
C
M
V
C
widgets
handlers
M
V
C
handlers
widgets
React
M
V
C
widgets
React
handlers
M
V
C
widgets
React
+ state
handlers
MVC
MVP
MVVC
MVVM
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"
https://xkcd.com/927/
“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
https://xkcd.com/927/
https://www.youtube.com/watch?v=-jwQ3sGoiXg
https://www.youtube.com/watch?v=-jwQ3sGoiXg
https://www.youtube.com/watch?v=-jwQ3sGoiXg
Origin I - Flux
Flux
21
https://facebook.github.io/flux/
1 * *
*
Flux
// dispatcher.js
const AppDispatcher = new Dispatcher();
export default AppDispatcher;
// Dispatcher API
// AppDispatcher.register(function(action) { ... });
// AppDispatcher.dispatch(action);
22
1
Flux
// stores/todos.js
const _todos = [];
const TodoStore = {...EventEmitter.prototype, {
getAll() {
return _todos;
},
});
AppDispatcher.register(function(action) {
...
TodoStore.emit('change');
});
export default TodoStore;
23
*
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() { ... }
}
24
*
Flux
// components/AddTodo.js
class AddTodo extends React.Component {
constructor() { ... }
handleClick = () => {
AppDispatcher.dispatch({
type: 'ADD_TODO',
text: this.state.value,
});
}
render() { ... }
}
25
*
Origin II - Flux Refactor
27
https://www.youtube.com/watch?v=xsSnOQynTHs
Inspired in Flux
• Like Flux:
• Single direction data-flow
• Has actions
• Actions are dispatched
• Has stores concept
• Views subscribes to stores
28
Inspired in Flux
• Unlike Flux:
• One single store
> Single source of truth
• No dispatcher (dispatch by Store)
• State is computed with reducers
> State is read-only
> Changes are pure functions
• State is injected to views
29
Flux to Redux
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
Flux to Redux
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;
31
Flux to Redux
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
Flux to Redux
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;
33
Flux to Redux
let _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
Flux to Redux
let _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;
35
Flux to Redux
let _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
Flux to Redux
let _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;
37
Flux to Redux
let _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
Flux to Redux
let _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];
}
});
39
Flux to Redux
let _todos = [];
export function handle(action) {
switch (action.type) {
case ActionTypes.TODO_CREATE:
let text = action.text.trim();
_todos = [..._todos, text];
}
return _todos;
});
40
Flux to Redux
let _todos = [];
export function handle(action) {
switch (action.type) {
case ActionTypes.TODO_CREATE:
let text = action.text.trim();
_todos = [..._todos, text];
}
return _todos;
});
41
Flux to Redux
export function handle(_todos, action) {
switch (action.type) {
case ActionTypes.TODO_CREATE:
let text = action.text.trim();
_todos = [..._todos, text];
}
return _todos;
});
42
Flux to Redux
export function handle(_todos, action) {
switch (action.type) {
case ActionTypes.TODO_CREATE:
let text = action.text.trim();
_todos = [..._todos, text];
}
return _todos;
});
43
Flux to Redux
export function handle(state, action) {
switch (action.type) {
case ActionTypes.TODO_CREATE:
let text = action.text.trim();
state = [...state, text];
}
return state;
});
44
Flux to Redux
export function handle(state, action) {
switch (action.type) {
case ActionTypes.TODO_CREATE:
let text = action.text.trim();
state = [...state, text];
}
return state;
});
45
Flux to Redux
export function handle(state, action) {
switch (action.type) {
case ActionTypes.TODO_CREATE:
let text = action.text.trim();
return [...state, text];
default:
return state;
}
});
46
Flux to Redux
export function handle(state = [], action) {
switch (action.type) {
case ActionTypes.TODO_CREATE:
let text = action.text.trim();
return [..._todos, text];
default:
return state;
}
});
47
Redux 101
Actions
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};
};
50
http://redux.js.org/docs/basics/Actions.html#source-code
State
State
// state is one single JSON object
{
visibilityFilter: 'SHOW_ALL',
todos: [
{
id: 123,
text: 'Learn Redux',
completed: false,
},
],
}
// state should be normalized (use pk/fk)
52
Reducers
Compute Action
// execute an action is like:
(previousState, action) => newState
54
Compute Action
const reducer = (previousState, action) => newState
55
Compute Action
const reducer = (previousState, action) => newState
const actions = [
addTodo('buy redux book'),
addTodo('read redux book'),
toggleTodo(3),
setVisibilityFilter(SHOW_ACTIVE),
...
];
56
Compute Action
const 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
Reducer
const reducer = (state, action) => {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return {
...state,
visibilityFilter: action.filter,
};
}
};
58
Reducer
const reducer = (state, action) => {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return {
...state,
visibilityFilter: action.filter,
};
default:
return state;
}
};
59
Reducer
const reducer = (state, action) => {
switch (action.type) {
case SET_VISIBILITY_FILTER: ...
case ADD_TODO:
return {
...state,
todos: [
...state.todos,
{ text: action.text, completed: false },
],
};
default:
return state;
}
};
60
Reducer
const 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: ...
}
};
61
Initial State
const reducer = (previousState, action) => newState
const actions = [
addTodo('buy redux book'),
addTodo('read redux book'),
toggleTodo(3),
setVisibilityFilter(SHOW_ACTIVE),
...
];
const finalAppState = actions.reduce(reducer);
62
Something is missing: https://developer.mozilla.org/en-US/docs/Web/
JavaScript/Reference/Global_Objects/Array/Reduce#Syntax
Initial State
const reducer = (previousState, action) => newState
const actions = [ ... ];
const initialState = {
visibilityFilter: SHOW_ALL,
todos: [],
};
const finalAppState =
actions.reduce(reducer, initialState);
63
Initial State
const reducer = (previousState, action) => newState
const actions = [ ... ];
const initialState = {
visibilityFilter: SHOW_ALL,
todos: [],
};
const finalAppState =
actions.reduce(reducer, undefined);
64
Reducers
const initialState = { ... };
const reducer = (state, action) => {
if (state === undefined) {
state = initialState;
}
switch (action.type) {
case SET_VISIBILITY_FILTER: ...
case ADD_TODO: ...
case TOGGLE_TODO: ...
default: ...
}
};
65
Reducers
const initialState = { ... };
const reducer = (state = initialState, action) => {
switch (action.type) {
case SET_VISIBILITY_FILTER: ...
case ADD_TODO: ...
case TOGGLE_TODO: ...
default: ...
}
};
66
Splitting Reducers
const initialState = { ... };
const reducer = (state = initialState, action) => {
switch (action.type) {
case SET_VISIBILITY_FILTER: ...
case ADD_TODO: ...
case TOGGLE_TODO: ...
default: ...
}
};
67
Splitting Reducers
const 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: ...
}
};
68
Splitting Reducers
const initialState = { ... };
const reducer = (state = initialState, action) => {
state = visibilityFilter(state, action);
state = todos(state, action);
return state;
};
69
Splitting Reducers
const initialState = { ... };
const reducer = (state = initialState, action) => {
return {
visibilityFilter: visibilityFilter(
state.visibilityFilter, action),
todos: todos(state.todos, action)
};
};
70
Splitting Reducers
const reducer = (state = {}, action) => {
return {
visibilityFilter: visibilityFilter(
state.visibilityFilter, action),
todos: todos(state.todos, action)
};
};
71
Splitting Reducers
const visibilityFilter = (state = SHOW_ALL, action) =>
{
switch (action.type) {
case SET_VISIBILITY_FILTER:
return action.filter;
default:
return state;
}
};
72
Splitting Reducers
const 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;
}
};
73
Combine Reducers
const todoApp = (state = {}, action) => {
return {
visibilityFilter: visibilityFilter(
state.visibilityFilter, action),
todos: todos(state.todos, action)
};
};
74
Combine Reducers
const todoApp = (state = {}, action) => {
// Wrong!: creates new object even if nothing changes
return {
visibilityFilter: visibilityFilter(
state.visibilityFilter, action),
todos: todos(state.todos, action)
};
};
75
Combine Reducers
import { combineReducers } from 'redux';
const todoApp = combineReducers({
visibilityFilter,
todos
});
76
http://redux.js.org/docs/basics/Reducers.html#source-code
Store
Store
import { createStore } from 'redux';
const store = createStore(reducer/*, initialState?*/);
// store.getState(): state
// store.dispatch(action)
// store.subscribe(listener): unsubscribeFn
78
http://redux.js.org/docs/basics/Store.html
Dispatching actions
// Do you remember?
const actions = [
addTodo('buy redux book'),
addTodo('read redux book'),
toggleTodo(3),
setVisibilityFilter(SHOW_ACTIVE),
...
];
79
Dispatching 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));
80
Redux + React
Bindings
$ npm install --save react-redux
82
http://redux.js.org/docs/basics/UsageWithReact.html
Presentational vs Container
83
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
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
84
Presentational Component
function Toggle(props) {
return (
<span onClick={props.onToggle}>
{props.toggle ? 'on' : 'off'}
</span>
);
}
85
Container Component
const mapStateToProps = (state) => ({
toggle: state,
});
const mapDispatchToProps = (dispatch, ownProps) => ({
onToggle: () => dispatch(toggle()),
});
const ToggleContainer = ReactRedux.connect(
mapStateToProps,
mapDispatchToProps
)(Toggle);
86
Providing the Store
import { Provider } from 'react-redux';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
87
Middleware
Middleware
• What if we want log all actions?
• Log previous state
• Log dispatched action
• Log resulting state
89
Middleware
let action = addTodo('Use Redux')
console.log('dispatching', action)
store.dispatch(action)
console.log('next state', store.getState())
90
Middleware
function dispatchAndLog(store, action) {
console.log('dispatching', action)
store.dispatch(action)
console.log('next state', store.getState())
}
dispatchAndLog(store, addTodo('Use Redux'))
91
Middleware
• What if we also want save change states?
• What if we also want report crashes?
• What if we also want to time travel?
92
Middleware
const logger = (store) => (next) => (action) => {
console.log('dispatching', action);
let result = next(action);
console.log('next state', store.getState());
return result;
}
const stateSaver = (store) => (next) => (action) => ...;
const crashReporter = (store) => (next) => (action) => ...;
const timeTraveler = (store) => (next) => (action) => ...;
93
Middleware
const 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'))
94
Middleware
const middlewares = [
logger, // 3
stateSaver, // 2
crashReporter, // 1
timeTraveler, // 0
];
const next = (action) => middlewares.reduce(
(next, middleware) => middleware(store)(next)
, store.dispatch);
next(addTodo('Use Redux'));
95
Middleware
const middlewares = [
timeTraveler, // 0
crashReporter, // 1
stateSaver, // 2
logger, // 3
];
const next = (action) => middlewares.reduceRight(
(next, middleware) => middleware(store)(next)
, store.dispatch);
next(addTodo('Use Redux'));
96
Compose
const 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 :).
97
Enhancer
import { applyMiddleware, createStore } from 'redux';
const store = createStore(
todoApp,
/* initialState, */
applyMiddleware(
timeTraveler, // 0
crashReporter, // 1
stateSaver, // 2
logger, // 3
)
);
98
Async
Asynchronous
const PING = 'PING';
const PONG = 'PONG';
const ping = () => ({ type: PING });
const pong = () => ({ type: PONG });
export function pingPong() {
// first ping(), then 1 sec later, pong()
}
100
Asynchronous
const PING = 'PING';
const PONG = 'PONG';
const ping = () => ({ type: PING });
const pong = () => ({ type: PONG });
export function pingPong() {
setTimeout(() => dispatch(pong()), 1000);
return ping();
}
101
Async - Thunk
Asynchronous
const PING = 'PING';
const PONG = 'PONG';
const ping = () => ({ type: PING });
const pong = () => ({ type: PONG });
function pingPong() {
return (dispatch) => {
setTimeout(() => dispatch(pong()), 1000);
return ping();
};
}
103
Recap
const middleware = (store) => (next) => (action) => {
return next(action);
};
104
"Inject" dispatch
const middleware = (store) => (next) => (action) => {
if (typeof action === 'function') {
return action(
store.dispatch,
store.getState
);
}
return next(action);
};
105
Redux-thunk
const thunkMiddleware = (store) => (next) => (action) => {
if (typeof action === 'function') {
return action(
store.dispatch,
store.getState
);
}
return next(action);
};
106
Async - Async-Thunk
Asynchronous
const PING = 'PING';
const PONG = 'PONG';
const ping = () => ({ type: PING });
const pong = () => ({ type: PONG });
function pingPong() {
return (dispatch) => {
setTimeout(() => dispatch(pong()), 1000);
dispatch(ping());
};
}
108
Asynchronous
const PING = 'PING';
const PONG = 'PONG';
const ping = () => ({ type: PING });
const pong = () => ({ type: PONG });
function pingPong() {
return (dispatch) => {
dispatch(ping());
setTimeout(() => dispatch(pong()), 1000);
};
}
109
Asynchronous
const PING = 'PING';
const PONG = 'PONG';
const ping = () => ({ type: PING });
const pong = () => ({ type: PONG });
function pingPong() {
return async (dispatch) => {
dispatch(ping());
await delay(1000);
dispatch(pong());
};
}
110
Redux-thunk
const asyncThunkMiddleware =
(store) => (next) => async (action) =>
{
if (typeof action === 'function') {
return await action(
store.dispatch,
store.getState,
);
}
return next(action);
};
111
Async - Others
Others
• https://redux-saga.js.org/
• https://redux-observable.js.org/
113
Remote I/O
Actions
{ type: 'FETCH_POSTS' }
{
type: 'FETCH_POSTS',
status: 'error',
error: 'Ops'
}
{
type: 'FETCH_POSTS',
status: 'success',
response: { ... }
}
115
Actions
{ type: 'FETCH_POSTS_REQUEST' }
{
type: 'FETCH_POSTS_FAILURE',
error: 'Ops'
}
{
type: 'FETCH_POSTS_SUCCESS',
response: { ... }
}
116
Actions
const requestPosts = () => ({
type: 'FETCH_POSTS_REQUEST'
});
const requestPostsFailure = (error) => ({
type: 'FETCH_POSTS_FAILURE',
error
});
const receivePosts = (response) => ({
type: 'FETCH_POSTS_SUCCESS',
response
});
117
Actions
const fetchPostsRequest = () => ({
type: 'FETCH_POSTS_REQUEST'
});
const fetchPostsFailure = (error) => ({
type: 'FETCH_POSTS_FAILURE',
error
});
const fetchPostsSuccess = (response) => ({
type: 'FETCH_POSTS_SUCCESS',
response
});
118
Async Action
const fetchPosts => () => async (dispatch) => {
dispatch(fetchPostsRequest());
try {
const response = await apiGet('/posts');
dispatch(fetchPostsSuccess(response));
} catch (error) {
dispatch(fetchPostsFailure(error));
}
}
119
Managing State
Normalized Data
http://redux.js.org/docs/recipes/reducers/NormalizingStateShape.html
http://redux.js.org/docs/recipes/reducers/UpdatingNormalizedData.html
Data Example
{
"id": "123",
"author": {
"id": "1",
"name": "Paul"
},
"title": "My awesome blog post",
"comments": [
{
"id": "324",
"commenter": {
"id": "2",
"name": "Nicole"
}
}
]
}
122
Unnormalized Data
{
"id": "123",
"author": {
"id": "1",
"name": "Paul"
},
"title": "My awesome blog post",
"comments": [
{
"id": "324",
"commenter": {
"id": "2",
"name": "Nicole"
}
}
]
}
123
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",
}
}
124
Normalized State
const state = {
simpleData: ...,
entities: {
posts: { ... },
comments: { ... },
users: { ... },
},
ui: {
selectedComments: [ ... ]
}
};
125
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});
126
Normalized Updates
function addComment(postId, commentText) {
const commentId = generateId('comment');
return {
type: "ADD_COMMENT",
postId,
commentId,
commentText,
};
};
127
Normalized Updates
const comments = (state = {}, action) => {
...
case ADD_COMMENT:
return {
...state,
[action.commentId]: {
id: action.commentId,
text: action.commentText,
}
};
...
};
128
Normalized Updates
const posts = (state = {}, action) => {
...
case ADD_COMMENT:
const post = state[action.postId];
return {
...state,
[post.id]: {
...post,
comments: [...post.comments, action.commentId],
}
};
...
};
129
Normalized State Alt.
const state = {
simpleData: ...,
entities: {
posts: { byIds: {...}, allIds: [...] },
comments: { byIds: {...}, allIds: [...] },
users: { byIds: {...}, allIds: [...] },
},
ui: {
selectedComments: [ ... ]
}
};
130
Computed Data
http://redux.js.org/docs/recipes/ComputingDerivedData.html
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
132
Using reselect
$ npm install --save reselect
133
Create Selector
import { 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)
}
}
);
134
Selectors of Selectors
const getKeyword = (state) => state.keyword
const getVisibleTodosFilteredByKeyword = createSelector(
[ getVisibleTodos, getKeyword ],
( visibleTodos, keyword ) => {
return visibleTodos.filter(
todo => todo.text.indexOf(keyword) > -1
);
)
)
135
Selectors and Containers
const mapStateToProps = (state) => ({
todos: getVisibleTodos(state),
});
const mapDispatchToProps = (dispatch) => ({
onTodoClick: (id) => {
dispatch(toggleTodo(id))
},
});
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList);
136

More Related Content

What's hot (20)

PDF
React, Redux and es6/7
Dongho Cho
 
PDF
JDK Power Tools
Tobias Lindaaker
 
PDF
Reactive, component 그리고 angular2
Jeado Ko
 
PDF
Adventures In JavaScript Testing
Thomas Fuchs
 
PDF
Workshop 5: JavaScript testing
Visual Engineering
 
PDF
GeeCON 2017 - TestContainers. Integration testing without the hassle
Anton Arhipov
 
PDF
Making the most of your gradle build - Greach 2017
Andres Almiray
 
PDF
Making the most of your gradle build - Gr8Conf 2017
Andres Almiray
 
PDF
TDD CrashCourse Part4: Improving Testing
David Rodenas
 
PDF
Spock Testing Framework - The Next Generation
BTI360
 
PDF
Celery
Òscar Vilaplana
 
KEY
Inside PyMongo - MongoNYC
Mike Dirolf
 
KEY
Why ruby
rstankov
 
PDF
33rd Degree 2013, Bad Tests, Good Tests
Tomek Kaczanowski
 
KEY
Ruby/Rails
rstankov
 
PDF
Testing Java Code Effectively
Andres Almiray
 
PDF
Maintainable JavaScript 2011
Nicholas Zakas
 
RTF
Easy Button
Adam Dale
 
PDF
Nativescript angular
Christoffer Noring
 
PDF
Graphql, REST and Apollo
Christoffer Noring
 
React, Redux and es6/7
Dongho Cho
 
JDK Power Tools
Tobias Lindaaker
 
Reactive, component 그리고 angular2
Jeado Ko
 
Adventures In JavaScript Testing
Thomas Fuchs
 
Workshop 5: JavaScript testing
Visual Engineering
 
GeeCON 2017 - TestContainers. Integration testing without the hassle
Anton Arhipov
 
Making the most of your gradle build - Greach 2017
Andres Almiray
 
Making the most of your gradle build - Gr8Conf 2017
Andres Almiray
 
TDD CrashCourse Part4: Improving Testing
David Rodenas
 
Spock Testing Framework - The Next Generation
BTI360
 
Inside PyMongo - MongoNYC
Mike Dirolf
 
Why ruby
rstankov
 
33rd Degree 2013, Bad Tests, Good Tests
Tomek Kaczanowski
 
Ruby/Rails
rstankov
 
Testing Java Code Effectively
Andres Almiray
 
Maintainable JavaScript 2011
Nicholas Zakas
 
Easy Button
Adam Dale
 
Nativescript angular
Christoffer Noring
 
Graphql, REST and Apollo
Christoffer Noring
 

Similar to Redux for ReactJS Programmers (20)

PPTX
Maintaining sanity in a large redux app
Nitish Kumar
 
PDF
Manage the Flux of your Web Application: Let's Redux
Commit University
 
PDF
[@NaukriEngineering] Flux Architecture
Naukri.com
 
PDF
Redux essentials
Chandan Kumar Rana
 
PDF
Григорий Шехет "Treasure hunt in the land of Reactive frameworks"
Fwdays
 
PDF
*X ARCHITECTURES
Gabriele Falasca
 
PPTX
Reducers+flux=redux
Shmulik Chicvashvili
 
PPTX
Redux training
dasersoft
 
PDF
Building React Applications with Redux
FITC
 
PDF
Redux
Fernando Souza
 
PDF
Redux js
Nils Petersohn
 
PDF
Intro to Redux | DreamLab Academy #3
DreamLab
 
PPTX
Academy PRO: React JS. Redux & Tooling
Binary Studio
 
PPTX
React. Flux. Redux
Andrey Kolodnitsky
 
PDF
The Road To Redux
Jeffrey Sanchez
 
PDF
From Back to Front: Rails To React Family
Khor SoonHin
 
PPTX
Getting started with Redux js
Citrix
 
PDF
React lecture
Christoffer Noring
 
PDF
An Introduction to Redux
NexThoughts Technologies
 
PDF
Reactивная тяга
Vitebsk Miniq
 
Maintaining sanity in a large redux app
Nitish Kumar
 
Manage the Flux of your Web Application: Let's Redux
Commit University
 
[@NaukriEngineering] Flux Architecture
Naukri.com
 
Redux essentials
Chandan Kumar Rana
 
Григорий Шехет "Treasure hunt in the land of Reactive frameworks"
Fwdays
 
*X ARCHITECTURES
Gabriele Falasca
 
Reducers+flux=redux
Shmulik Chicvashvili
 
Redux training
dasersoft
 
Building React Applications with Redux
FITC
 
Redux js
Nils Petersohn
 
Intro to Redux | DreamLab Academy #3
DreamLab
 
Academy PRO: React JS. Redux & Tooling
Binary Studio
 
React. Flux. Redux
Andrey Kolodnitsky
 
The Road To Redux
Jeffrey Sanchez
 
From Back to Front: Rails To React Family
Khor SoonHin
 
Getting started with Redux js
Citrix
 
React lecture
Christoffer Noring
 
An Introduction to Redux
NexThoughts Technologies
 
Reactивная тяга
Vitebsk Miniq
 
Ad

More from David Rodenas (18)

PDF
TDD CrashCourse Part2: TDD
David Rodenas
 
PDF
TDD CrashCourse Part1: Testing
David Rodenas
 
PDF
TDD CrashCourse Part3: TDD Techniques
David Rodenas
 
PDF
TDD CrashCourse Part5: Testing Techniques
David Rodenas
 
PDF
Be professional: We Rule the World
David Rodenas
 
PDF
ES3-2020-P2 Bowling Game Kata
David Rodenas
 
PDF
ES3-2020-05 Testing
David Rodenas
 
PDF
Testing, Learning and Professionalism — 20171214
David Rodenas
 
PDF
Vespres
David Rodenas
 
PDF
Faster web pages
David Rodenas
 
PPTX
From high school to university and work
David Rodenas
 
PDF
Modules in angular 2.0 beta.1
David Rodenas
 
PDF
Freelance i Enginyeria
David Rodenas
 
PDF
Angular 1.X Community and API Decissions
David Rodenas
 
PDF
MVS: An angular MVC
David Rodenas
 
PDF
Mvc - Model: the great forgotten
David Rodenas
 
PDF
(automatic) Testing: from business to university and back
David Rodenas
 
PDF
Testing: ¿what, how, why?
David Rodenas
 
TDD CrashCourse Part2: TDD
David Rodenas
 
TDD CrashCourse Part1: Testing
David Rodenas
 
TDD CrashCourse Part3: TDD Techniques
David Rodenas
 
TDD CrashCourse Part5: Testing Techniques
David Rodenas
 
Be professional: We Rule the World
David Rodenas
 
ES3-2020-P2 Bowling Game Kata
David Rodenas
 
ES3-2020-05 Testing
David Rodenas
 
Testing, Learning and Professionalism — 20171214
David Rodenas
 
Vespres
David Rodenas
 
Faster web pages
David Rodenas
 
From high school to university and work
David Rodenas
 
Modules in angular 2.0 beta.1
David Rodenas
 
Freelance i Enginyeria
David Rodenas
 
Angular 1.X Community and API Decissions
David Rodenas
 
MVS: An angular MVC
David Rodenas
 
Mvc - Model: the great forgotten
David Rodenas
 
(automatic) Testing: from business to university and back
David Rodenas
 
Testing: ¿what, how, why?
David Rodenas
 
Ad

Recently uploaded (20)

DOCX
Import Data Form Excel to Tally Services
Tally xperts
 
PDF
Efficient, Automated Claims Processing Software for Insurers
Insurance Tech Services
 
PPTX
Engineering the Java Web Application (MVC)
abhishekoza1981
 
PPTX
Perfecting XM Cloud for Multisite Setup.pptx
Ahmed Okour
 
PDF
Thread In Android-Mastering Concurrency for Responsive Apps.pdf
Nabin Dhakal
 
PDF
Revenue streams of the Wazirx clone script.pdf
aaronjeffray
 
PDF
Mobile CMMS Solutions Empowering the Frontline Workforce
CryotosCMMSSoftware
 
PDF
Beyond Binaries: Understanding Diversity and Allyship in a Global Workplace -...
Imma Valls Bernaus
 
PDF
Linux Certificate of Completion - LabEx Certificate
VICTOR MAESTRE RAMIREZ
 
PDF
Salesforce CRM Services.VALiNTRY360
VALiNTRY360
 
PDF
Powering GIS with FME and VertiGIS - Peak of Data & AI 2025
Safe Software
 
PPTX
Why Businesses Are Switching to Open Source Alternatives to Crystal Reports.pptx
Varsha Nayak
 
PDF
iTop VPN With Crack Lifetime Activation Key-CODE
utfefguu
 
PPTX
A Complete Guide to Salesforce SMS Integrations Build Scalable Messaging With...
360 SMS APP
 
PPTX
3uTools Full Crack Free Version Download [Latest] 2025
muhammadgurbazkhan
 
PPTX
Writing Better Code - Helping Developers make Decisions.pptx
Lorraine Steyn
 
PDF
Letasoft Sound Booster 1.12.0.538 Crack Download+ Product Key [Latest]
HyperPc soft
 
PDF
Capcut Pro Crack For PC Latest Version {Fully Unlocked} 2025
hashhshs786
 
PPTX
Human Resources Information System (HRIS)
Amity University, Patna
 
PDF
Continouous failure - Why do we make our lives hard?
Papp Krisztián
 
Import Data Form Excel to Tally Services
Tally xperts
 
Efficient, Automated Claims Processing Software for Insurers
Insurance Tech Services
 
Engineering the Java Web Application (MVC)
abhishekoza1981
 
Perfecting XM Cloud for Multisite Setup.pptx
Ahmed Okour
 
Thread In Android-Mastering Concurrency for Responsive Apps.pdf
Nabin Dhakal
 
Revenue streams of the Wazirx clone script.pdf
aaronjeffray
 
Mobile CMMS Solutions Empowering the Frontline Workforce
CryotosCMMSSoftware
 
Beyond Binaries: Understanding Diversity and Allyship in a Global Workplace -...
Imma Valls Bernaus
 
Linux Certificate of Completion - LabEx Certificate
VICTOR MAESTRE RAMIREZ
 
Salesforce CRM Services.VALiNTRY360
VALiNTRY360
 
Powering GIS with FME and VertiGIS - Peak of Data & AI 2025
Safe Software
 
Why Businesses Are Switching to Open Source Alternatives to Crystal Reports.pptx
Varsha Nayak
 
iTop VPN With Crack Lifetime Activation Key-CODE
utfefguu
 
A Complete Guide to Salesforce SMS Integrations Build Scalable Messaging With...
360 SMS APP
 
3uTools Full Crack Free Version Download [Latest] 2025
muhammadgurbazkhan
 
Writing Better Code - Helping Developers make Decisions.pptx
Lorraine Steyn
 
Letasoft Sound Booster 1.12.0.538 Crack Download+ Product Key [Latest]
HyperPc soft
 
Capcut Pro Crack For PC Latest Version {Fully Unlocked} 2025
hashhshs786
 
Human Resources Information System (HRIS)
Amity University, Patna
 
Continouous failure - Why do we make our lives hard?
Papp Krisztián
 

Redux for ReactJS Programmers

  • 3. MVC
  • 6. 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
  • 7. 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 {
  • 13. MVC MVP MVVC MVVM 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"
  • 15. “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
  • 20. Origin I - Flux
  • 22. Flux // dispatcher.js const AppDispatcher = new Dispatcher(); export default AppDispatcher; // Dispatcher API // AppDispatcher.register(function(action) { ... }); // AppDispatcher.dispatch(action); 22 1
  • 23. Flux // stores/todos.js const _todos = []; const TodoStore = {...EventEmitter.prototype, { getAll() { return _todos; }, }); AppDispatcher.register(function(action) { ... TodoStore.emit('change'); }); export default TodoStore; 23 *
  • 24. 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() { ... } } 24 *
  • 25. Flux // components/AddTodo.js class AddTodo extends React.Component { constructor() { ... } handleClick = () => { AppDispatcher.dispatch({ type: 'ADD_TODO', text: this.state.value, }); } render() { ... } } 25 *
  • 26. Origin II - Flux Refactor
  • 28. Inspired in Flux • Like Flux: • Single direction data-flow • Has actions • Actions are dispatched • Has stores concept • Views subscribes to stores 28
  • 29. Inspired in Flux • Unlike Flux: • One single store > Single source of truth • No dispatcher (dispatch by Store) • State is computed with reducers > State is read-only > Changes are pure functions • State is injected to views 29
  • 30. Flux to Redux 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
  • 31. Flux to Redux 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; 31
  • 32. Flux to Redux 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
  • 33. Flux to Redux 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; 33
  • 34. Flux to Redux let _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
  • 35. Flux to Redux let _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; 35
  • 36. Flux to Redux let _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
  • 37. Flux to Redux let _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; 37
  • 38. Flux to Redux let _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
  • 39. Flux to Redux let _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]; } }); 39
  • 40. Flux to Redux let _todos = []; export function handle(action) { switch (action.type) { case ActionTypes.TODO_CREATE: let text = action.text.trim(); _todos = [..._todos, text]; } return _todos; }); 40
  • 41. Flux to Redux let _todos = []; export function handle(action) { switch (action.type) { case ActionTypes.TODO_CREATE: let text = action.text.trim(); _todos = [..._todos, text]; } return _todos; }); 41
  • 42. Flux to Redux export function handle(_todos, action) { switch (action.type) { case ActionTypes.TODO_CREATE: let text = action.text.trim(); _todos = [..._todos, text]; } return _todos; }); 42
  • 43. Flux to Redux export function handle(_todos, action) { switch (action.type) { case ActionTypes.TODO_CREATE: let text = action.text.trim(); _todos = [..._todos, text]; } return _todos; }); 43
  • 44. Flux to Redux export function handle(state, action) { switch (action.type) { case ActionTypes.TODO_CREATE: let text = action.text.trim(); state = [...state, text]; } return state; }); 44
  • 45. Flux to Redux export function handle(state, action) { switch (action.type) { case ActionTypes.TODO_CREATE: let text = action.text.trim(); state = [...state, text]; } return state; }); 45
  • 46. Flux to Redux export function handle(state, action) { switch (action.type) { case ActionTypes.TODO_CREATE: let text = action.text.trim(); return [...state, text]; default: return state; } }); 46
  • 47. Flux to Redux export function handle(state = [], action) { switch (action.type) { case ActionTypes.TODO_CREATE: let text = action.text.trim(); return [..._todos, text]; default: return state; } }); 47
  • 50. 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}; }; 50 http://redux.js.org/docs/basics/Actions.html#source-code
  • 51. State
  • 52. State // state is one single JSON object { visibilityFilter: 'SHOW_ALL', todos: [ { id: 123, text: 'Learn Redux', completed: false, }, ], } // state should be normalized (use pk/fk) 52
  • 54. Compute Action // execute an action is like: (previousState, action) => newState 54
  • 55. Compute Action const reducer = (previousState, action) => newState 55
  • 56. Compute Action const reducer = (previousState, action) => newState const actions = [ addTodo('buy redux book'), addTodo('read redux book'), toggleTodo(3), setVisibilityFilter(SHOW_ACTIVE), ... ]; 56
  • 57. Compute Action const 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
  • 58. Reducer const reducer = (state, action) => { switch (action.type) { case SET_VISIBILITY_FILTER: return { ...state, visibilityFilter: action.filter, }; } }; 58
  • 59. Reducer const reducer = (state, action) => { switch (action.type) { case SET_VISIBILITY_FILTER: return { ...state, visibilityFilter: action.filter, }; default: return state; } }; 59
  • 60. Reducer const reducer = (state, action) => { switch (action.type) { case SET_VISIBILITY_FILTER: ... case ADD_TODO: return { ...state, todos: [ ...state.todos, { text: action.text, completed: false }, ], }; default: return state; } }; 60
  • 61. Reducer const 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: ... } }; 61
  • 62. Initial State const reducer = (previousState, action) => newState const actions = [ addTodo('buy redux book'), addTodo('read redux book'), toggleTodo(3), setVisibilityFilter(SHOW_ACTIVE), ... ]; const finalAppState = actions.reduce(reducer); 62 Something is missing: https://developer.mozilla.org/en-US/docs/Web/ JavaScript/Reference/Global_Objects/Array/Reduce#Syntax
  • 63. Initial State const reducer = (previousState, action) => newState const actions = [ ... ]; const initialState = { visibilityFilter: SHOW_ALL, todos: [], }; const finalAppState = actions.reduce(reducer, initialState); 63
  • 64. Initial State const reducer = (previousState, action) => newState const actions = [ ... ]; const initialState = { visibilityFilter: SHOW_ALL, todos: [], }; const finalAppState = actions.reduce(reducer, undefined); 64
  • 65. Reducers const initialState = { ... }; const reducer = (state, action) => { if (state === undefined) { state = initialState; } switch (action.type) { case SET_VISIBILITY_FILTER: ... case ADD_TODO: ... case TOGGLE_TODO: ... default: ... } }; 65
  • 66. Reducers const initialState = { ... }; const reducer = (state = initialState, action) => { switch (action.type) { case SET_VISIBILITY_FILTER: ... case ADD_TODO: ... case TOGGLE_TODO: ... default: ... } }; 66
  • 67. Splitting Reducers const initialState = { ... }; const reducer = (state = initialState, action) => { switch (action.type) { case SET_VISIBILITY_FILTER: ... case ADD_TODO: ... case TOGGLE_TODO: ... default: ... } }; 67
  • 68. Splitting Reducers const 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: ... } }; 68
  • 69. Splitting Reducers const initialState = { ... }; const reducer = (state = initialState, action) => { state = visibilityFilter(state, action); state = todos(state, action); return state; }; 69
  • 70. Splitting Reducers const initialState = { ... }; const reducer = (state = initialState, action) => { return { visibilityFilter: visibilityFilter( state.visibilityFilter, action), todos: todos(state.todos, action) }; }; 70
  • 71. Splitting Reducers const reducer = (state = {}, action) => { return { visibilityFilter: visibilityFilter( state.visibilityFilter, action), todos: todos(state.todos, action) }; }; 71
  • 72. Splitting Reducers const visibilityFilter = (state = SHOW_ALL, action) => { switch (action.type) { case SET_VISIBILITY_FILTER: return action.filter; default: return state; } }; 72
  • 73. Splitting Reducers const 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; } }; 73
  • 74. Combine Reducers const todoApp = (state = {}, action) => { return { visibilityFilter: visibilityFilter( state.visibilityFilter, action), todos: todos(state.todos, action) }; }; 74
  • 75. Combine Reducers const todoApp = (state = {}, action) => { // Wrong!: creates new object even if nothing changes return { visibilityFilter: visibilityFilter( state.visibilityFilter, action), todos: todos(state.todos, action) }; }; 75
  • 76. Combine Reducers import { combineReducers } from 'redux'; const todoApp = combineReducers({ visibilityFilter, todos }); 76 http://redux.js.org/docs/basics/Reducers.html#source-code
  • 77. Store
  • 78. Store import { createStore } from 'redux'; const store = createStore(reducer/*, initialState?*/); // store.getState(): state // store.dispatch(action) // store.subscribe(listener): unsubscribeFn 78 http://redux.js.org/docs/basics/Store.html
  • 79. Dispatching actions // Do you remember? const actions = [ addTodo('buy redux book'), addTodo('read redux book'), toggleTodo(3), setVisibilityFilter(SHOW_ACTIVE), ... ]; 79
  • 80. Dispatching 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)); 80
  • 82. Bindings $ npm install --save react-redux 82 http://redux.js.org/docs/basics/UsageWithReact.html
  • 83. Presentational vs Container 83 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
  • 84. 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 84
  • 85. Presentational Component function Toggle(props) { return ( <span onClick={props.onToggle}> {props.toggle ? 'on' : 'off'} </span> ); } 85
  • 86. Container Component const mapStateToProps = (state) => ({ toggle: state, }); const mapDispatchToProps = (dispatch, ownProps) => ({ onToggle: () => dispatch(toggle()), }); const ToggleContainer = ReactRedux.connect( mapStateToProps, mapDispatchToProps )(Toggle); 86
  • 87. Providing the Store import { Provider } from 'react-redux'; ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') ); 87
  • 89. Middleware • What if we want log all actions? • Log previous state • Log dispatched action • Log resulting state 89
  • 90. Middleware let action = addTodo('Use Redux') console.log('dispatching', action) store.dispatch(action) console.log('next state', store.getState()) 90
  • 91. Middleware function dispatchAndLog(store, action) { console.log('dispatching', action) store.dispatch(action) console.log('next state', store.getState()) } dispatchAndLog(store, addTodo('Use Redux')) 91
  • 92. Middleware • What if we also want save change states? • What if we also want report crashes? • What if we also want to time travel? 92
  • 93. Middleware const logger = (store) => (next) => (action) => { console.log('dispatching', action); let result = next(action); console.log('next state', store.getState()); return result; } const stateSaver = (store) => (next) => (action) => ...; const crashReporter = (store) => (next) => (action) => ...; const timeTraveler = (store) => (next) => (action) => ...; 93
  • 94. Middleware const 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')) 94
  • 95. Middleware const middlewares = [ logger, // 3 stateSaver, // 2 crashReporter, // 1 timeTraveler, // 0 ]; const next = (action) => middlewares.reduce( (next, middleware) => middleware(store)(next) , store.dispatch); next(addTodo('Use Redux')); 95
  • 96. Middleware const middlewares = [ timeTraveler, // 0 crashReporter, // 1 stateSaver, // 2 logger, // 3 ]; const next = (action) => middlewares.reduceRight( (next, middleware) => middleware(store)(next) , store.dispatch); next(addTodo('Use Redux')); 96
  • 97. Compose const 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 :). 97
  • 98. Enhancer import { applyMiddleware, createStore } from 'redux'; const store = createStore( todoApp, /* initialState, */ applyMiddleware( timeTraveler, // 0 crashReporter, // 1 stateSaver, // 2 logger, // 3 ) ); 98
  • 99. Async
  • 100. Asynchronous const PING = 'PING'; const PONG = 'PONG'; const ping = () => ({ type: PING }); const pong = () => ({ type: PONG }); export function pingPong() { // first ping(), then 1 sec later, pong() } 100
  • 101. Asynchronous const PING = 'PING'; const PONG = 'PONG'; const ping = () => ({ type: PING }); const pong = () => ({ type: PONG }); export function pingPong() { setTimeout(() => dispatch(pong()), 1000); return ping(); } 101
  • 103. Asynchronous const PING = 'PING'; const PONG = 'PONG'; const ping = () => ({ type: PING }); const pong = () => ({ type: PONG }); function pingPong() { return (dispatch) => { setTimeout(() => dispatch(pong()), 1000); return ping(); }; } 103
  • 104. Recap const middleware = (store) => (next) => (action) => { return next(action); }; 104
  • 105. "Inject" dispatch const middleware = (store) => (next) => (action) => { if (typeof action === 'function') { return action( store.dispatch, store.getState ); } return next(action); }; 105
  • 106. Redux-thunk const thunkMiddleware = (store) => (next) => (action) => { if (typeof action === 'function') { return action( store.dispatch, store.getState ); } return next(action); }; 106
  • 108. Asynchronous const PING = 'PING'; const PONG = 'PONG'; const ping = () => ({ type: PING }); const pong = () => ({ type: PONG }); function pingPong() { return (dispatch) => { setTimeout(() => dispatch(pong()), 1000); dispatch(ping()); }; } 108
  • 109. Asynchronous const PING = 'PING'; const PONG = 'PONG'; const ping = () => ({ type: PING }); const pong = () => ({ type: PONG }); function pingPong() { return (dispatch) => { dispatch(ping()); setTimeout(() => dispatch(pong()), 1000); }; } 109
  • 110. Asynchronous const PING = 'PING'; const PONG = 'PONG'; const ping = () => ({ type: PING }); const pong = () => ({ type: PONG }); function pingPong() { return async (dispatch) => { dispatch(ping()); await delay(1000); dispatch(pong()); }; } 110
  • 111. Redux-thunk const asyncThunkMiddleware = (store) => (next) => async (action) => { if (typeof action === 'function') { return await action( store.dispatch, store.getState, ); } return next(action); }; 111
  • 115. Actions { type: 'FETCH_POSTS' } { type: 'FETCH_POSTS', status: 'error', error: 'Ops' } { type: 'FETCH_POSTS', status: 'success', response: { ... } } 115
  • 116. Actions { type: 'FETCH_POSTS_REQUEST' } { type: 'FETCH_POSTS_FAILURE', error: 'Ops' } { type: 'FETCH_POSTS_SUCCESS', response: { ... } } 116
  • 117. Actions const requestPosts = () => ({ type: 'FETCH_POSTS_REQUEST' }); const requestPostsFailure = (error) => ({ type: 'FETCH_POSTS_FAILURE', error }); const receivePosts = (response) => ({ type: 'FETCH_POSTS_SUCCESS', response }); 117
  • 118. Actions const fetchPostsRequest = () => ({ type: 'FETCH_POSTS_REQUEST' }); const fetchPostsFailure = (error) => ({ type: 'FETCH_POSTS_FAILURE', error }); const fetchPostsSuccess = (response) => ({ type: 'FETCH_POSTS_SUCCESS', response }); 118
  • 119. Async Action const fetchPosts => () => async (dispatch) => { dispatch(fetchPostsRequest()); try { const response = await apiGet('/posts'); dispatch(fetchPostsSuccess(response)); } catch (error) { dispatch(fetchPostsFailure(error)); } } 119
  • 122. Data Example { "id": "123", "author": { "id": "1", "name": "Paul" }, "title": "My awesome blog post", "comments": [ { "id": "324", "commenter": { "id": "2", "name": "Nicole" } } ] } 122
  • 123. Unnormalized Data { "id": "123", "author": { "id": "1", "name": "Paul" }, "title": "My awesome blog post", "comments": [ { "id": "324", "commenter": { "id": "2", "name": "Nicole" } } ] } 123
  • 124. 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", } } 124
  • 125. Normalized State const state = { simpleData: ..., entities: { posts: { ... }, comments: { ... }, users: { ... }, }, ui: { selectedComments: [ ... ] } }; 125
  • 126. 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}); 126
  • 127. Normalized Updates function addComment(postId, commentText) { const commentId = generateId('comment'); return { type: "ADD_COMMENT", postId, commentId, commentText, }; }; 127
  • 128. Normalized Updates const comments = (state = {}, action) => { ... case ADD_COMMENT: return { ...state, [action.commentId]: { id: action.commentId, text: action.commentText, } }; ... }; 128
  • 129. Normalized Updates const posts = (state = {}, action) => { ... case ADD_COMMENT: const post = state[action.postId]; return { ...state, [post.id]: { ...post, comments: [...post.comments, action.commentId], } }; ... }; 129
  • 130. Normalized State Alt. const state = { simpleData: ..., entities: { posts: { byIds: {...}, allIds: [...] }, comments: { byIds: {...}, allIds: [...] }, users: { byIds: {...}, allIds: [...] }, }, ui: { selectedComments: [ ... ] } }; 130
  • 132. 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 132
  • 133. Using reselect $ npm install --save reselect 133
  • 134. Create Selector import { 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) } } ); 134
  • 135. Selectors of Selectors const getKeyword = (state) => state.keyword const getVisibleTodosFilteredByKeyword = createSelector( [ getVisibleTodos, getKeyword ], ( visibleTodos, keyword ) => { return visibleTodos.filter( todo => todo.text.indexOf(keyword) > -1 ); ) ) 135
  • 136. Selectors and Containers const mapStateToProps = (state) => ({ todos: getVisibleTodos(state), }); const mapDispatchToProps = (dispatch) => ({ onTodoClick: (id) => { dispatch(toggleTodo(id)) }, }); const VisibleTodoList = connect( mapStateToProps, mapDispatchToProps )(TodoList); 136