Redux
§Zuzana Dankovčíková
-State management library
-Framework agnostic, but ideal for React apps
Why do we need Redux?
§We have already solved many problems of state management by
•treating data as immutable objects and
•having most of the data stored in the root component.
•
§
Problems:
- cycle in dependant mutations
- how to access data
-Props explosion?
-…
Problem 1: What is “root component”
§
§New feature request:
§à Displaying number of TODOs next to the avatar of the logged-in user?
§à “Unrelated” components dependent on the same data.
§à Lifting state up. But until when? How to make it scalable?
In a small app, it is easy. But what if it starts to grow?
Adding more pages to your single page app. TODO app is just one part of it. What if you wanted to
display number of TODOs in the navigation next to the logged-in user avatar?
Draw on the
whiteboard: http://mherman.org/node-workshop/slides/redux-intro/images/state-change-without-redux.j
pg
Problem 2: Callbacks chain
§
Simple click on save button when editing the component must bubble-up through 5 components in order
to change state of the root component. And this is still quite a small app.
Problem 2: Callbacks chain
§class TodoListContainer extends React.Component {
// other methods
// ...
render() {
return (
);
}
}
§
And as you see it is not just the problem of the onSave callback.
§
What is the solution? You introduce state management library such as Redux.
-> all state is stored in the single place but it is not the root component it is you app's store
deterministic and robust
-> components subscribe to store and only take data they are interested in
-> data and their presentation is decoupled won’t break on UI change
Motivation
§Complex state management made easy
•Scalable state management
•Deterministic and easily traceable changes
•State is decoupled from presentation (won’t break with every UI change)
•Better dev tools than console.log()
•Better testability
§
3 Principles of Redux
§Single source of truth:
§"The whole state of your app is stored in an object tree inside a single store."
§
§State is read-only:
§"The only way to change the state tree is to emit an action, an object describing what happened."
§
§Changes are made with pure functions:
§“To specify how the actions transform the state tree, you write pure reducers."
§
Other:
•Immutability
•State normalization
Building blocks
§Action
•describes UI changes
§
§Store
•receives action via dispatcher
•calls root reducer
§
§Reducer
•(prevState, action) => newState
§
§View
•gets notified about state change
•rerenders with new data
§
Draw separate image on the whiteboard and explain on one use case (adding new TODO).
There is 1 root reducer that can delegate state changes to other reducers.
One store entity managed by redux, takes care of state and is pretty much defined by reducers.
-> all user interactions are done by dispatching actions (saying what has happened)
-> store (its reducer) reacts to dispatched actions and update the state accordingly
-> subscribed components are notified about the changes and update accordingly
-> remember your view is only a function of data!
Observer Pattern, (Hollywood principle: “Don’t call us, we call you!”)
Demo introduction:
-Explain what we are going to do with our app
-Moving state from root component to redux store
-Move list of items to redux in DEMO, rest will be just shown as a final result
-Code along with the next slides, redux will be installed later, for now just actions and reducers
Actions & Action creators
§“Actions are payloads of information that send data from your application to your store. They are
the only source of information for the store.”
§
§A new developer can go through all defined actions and immediately see the entire API - all user
interactions that are possible in your app.
§
§
§
§17-redux-action-creators
{
type: 'TODO_LIST_ITEM_CREATE',
payload: {
id: 42,
text: 'Buy milk'
}
}
const createItem = (text) => ({
type: TODO_LIST_ITEM_CREATE,
payload: {
id: uuid(),
text: text
}
});
Action creator - helper function for creating actions
Action - simple JS objects describing data change
DEMO
-Define action types
-Explain why they need to be defined as constants in an extra file (glue between action and
reducer)
-Explain why they are prefixed with the name of the subapplication (need to be unique
application-wide, cos the root reducer recieves each action)
-TODO_LIST_ITEM_CREATE
-TODO_LIST_ITEM_DELETE
-TODO_LIST_ITEM_MOVE
-TODO_LIST_ITEM_UPDATE
-Write action creators
-Talk about need of having it in one place so that we do not define the actions by hand every time
in components
-It also serves as some kind of documentation of the UI capabilities
-createNewItem has whole item as payload and the id is generated there
Reducers
§Action describes WHAT has happened, reducer specifies HOW the state should change
§
•1 root reducer that can be composed from many others
•Pure function (prevState, action) => nextState
•
§
§What is a pure function? (args) => result
•It does not make outside network or database calls.
•Its return value depends solely on the values of its parameters.
•Its arguments should be considered "immutable" (must not be changed)
•Calling a pure function with the same set of arguments will always return the same value.
§
§
§
§
https://css-tricks.com/learning-react-redux/
Show some examples what is and what is not a pure function
Pure or impure?
§
§var count = 0;
cosnt increaseCount = (val) => count += val;
const time = () => new Date().toLocaleTimeString();
const addFive = (val) => val + 5;
const getMagicNumber = () => Math.random();
Excercise
Reducers
§Previous state argument
•Specify default value
•Return same reference for irrelevant action type
§18-redux-reducers
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
INITIAL STATE will be explained later.
What does a typical reducer look like?
- Always a pure function, never mutating arguments!
- Switch statement
- Always default statement returning previous state
- Always initial state (default argument, important for initial call by redux where whole state is
created)
DEMO
-Fix eslint indents for case statements ("indent": ["error", 4, {"SwitchCase": 1+ }],)
-Create reducers:
-ItemsList reducer (copy the case statements from txt file)
-Reducer composition on next slide
Reducer composition
§18-redux-reducers
§
DEMO
-Checkout commit with 18-redux-reducers
-Explain reducer composition
-Each reducer is responsible for some small part of the state
-If we wanted the updateItem could be implemented as separate reducer...
-Explain how this works with immutability (returining prevState on irrelevant action type)
-Create App and todoApp reducers
Store
§Single store for whole app managed by Redux (we only provide a root reducer)
§
•Holds application state;
•Allows access to state via getState();
•Allows state to be updated via dispatch(action);
•Registers listeners via subscribe(listener);
•Handles unregistering of listeners via the function returned by subscribe(listener).
•
§-- Redux docs
§
§
NO setters or direct access to the state object. All changes are described via actions and state
manipulation is defined by reducers as pure functions.
Minimalistic API
§
•createStore(rootReducer)
•
•store.getState()
•store.dispatch(action)
•store.subscribe(listener)
•
•combineReducers({…})
•
•
•What is the store lifecycle?
§à initial call to reducer + call on every dispatched action
§19-install-redux
Most of the time you only write pure JS functions.
What does createStore() do?? It calls the root reducer with undefined, thus the state is populated
with default values of the prevState function argument.
DEMO – checkout, show app.jsx
-Npm install –save redux
-Import createStore to app.jsx
-Copy contents of utils/getItems() and initialize store state
-Console.log(store.getState())
-Use combineReducers() for app and todoApp reducers
-Explain lifecycle -> call to createStore() calls the root reducer with undefined and sets the
result value as the default state
React-redux integration
§You can connect your existing app to the store by hand.
§But you would loose many optimizations react-redux package brings.
§
§Use react-redux library instead:
1.Wrap your root component in
2.Connect components to redux store
•connect(mapStateToProps, mapDispatchToProps)(Component)
§20-connect-root-component
Make sure to explain syntax connect(…)(Component)
DEMO
-Npm install –save react-redux
-Clean up app.jsx, (const initialState = { todoApp: { itemsList: getInitialItems() } };)
-Wrap the root component in Provider
-Create a container in containers-redux folder
-Carefully update container/TodoList
-https://github.com/KenticoAcademy/PV247-2017/commit/7730e35156612e677daa8ba0d3957d19bc5a2b57
-At the end of the lecture the file will be deleted completely
-Define prop types (list, onCreateNew, onUpdate, onDelete, onMove)
-Delete part of state
-Remove _getDefaultTodoList
-Rewrite all state.list to props.list
-Carefull about _createNewItem method and _updateItem
Moving more state to the Redux store
§All state from the root component shall be moved to the store
•New actions,
•New reducers
•No internal state in TodoList.jsx
§à the old container is basically useless
•
§
§21-move-state-to-store
DEMO:
Checkout next commits where all the state from TodoList component is moved to redux store.
Show how it looks like.
Notice editedItemId - not only explicit actions to change store but ALL actions we are interested
in -> saving /updating edited item closes the editor
Be declarative
§
•const editedItemId = (state = null, action) => {
switch(action.type) {
case TODO_LIST_ITEM_START_EDITING:
return action.payload.id;
• case TODO_LIST_ITEM_CANCEL_EDITING:
case TODO_LIST_ITEM_UPDATE:
case TODO_LIST_ITEM_DELETE:
return null;
default:
return null;
}
};
Action describes what has happened, reducer decides how to react
dispatch({
type: 'SET_EDITED_ITEM_ID',
payload: {
id: 42
}
});
dispatch({
type: 'CLEAR_EDITED_ITEM_ID'
});
Image result for no png Image result for no png
Declarative vs imperative approach.
You want your actions to say what has happened on the UI, NOT telling reducers what to do.
Should all components be stateless?
§“How much” state should we move to the redux store?
§
§Does your state influence more components in your application?
à(and the common parent is way up in the hierarchy)
àmove state to redux store
àTodoList.jsx
§Is the state well encapsulated and local for the component?
àIt can stay in the stateful component.
àTodoListEditedItem.jsx
§
§
Statefull vs Stateless:
Explain on our todo app..
E.g. TodoListEditedItem
Until user clicks save, no temporary state is relevant for other parts of the application… at max
they may be interesting in knowing one of the components is being edited to disable some other
functions.
Benefits
§State described as plain object and arrays:
•Inject initial state during server rendering
•Persist to and load from localStorage
•UI is function of state (state -> UI -> deterministic behavior)
•Immutability (React performance)
§
§State changes described as plain objects
•Replaying the history (reproducing bugs)
•Pass actions over network in collaborative environments (google docs, trello live updates)
•Implementing undo
•Awesome tooling
§
§State modification as pure functions
•Testability
•Hot reloading
•
§3rd party modules integration (middleware, libs that need to store state...)
§
Hot reloading -> show image from code
cartoons: https://code-cartoons.com/a-cartoon-intro-to-redux-3afb775501a6
Time travel debugging -> show example
Middleware support -> logging etc.
Drawbacks
•Boilerplate & Verbosity
§-> have a look at Repatch
§
•"One huge object"
§-> pretty much eliminated by reducer composition and ImmutableJS
§
•"Component state vs Redux store" dilema
§-> see #1287 and: "Do whatever is less akward."
•
§
3 Principles of Redux - revised
§Single source of truth:
§"The whole state of your app is stored in an object tree inside a single store."
§
§State is read-only:
§"The only way to change the state tree is to emit an action, an object describing what happened."
§
§Changes are made with pure functions:
§“To specify how the actions transform the state tree, you write pure reducers."
§
Part 2
§
What about our props explosion?
§
§
§
§
§
§
§
§
§
§
§
§
§
§
We haven’t helped the callback chain presented at the beginning of the lecture very much.
Motivation for connecting more than just root component to redux store.
Connecting more components
§
Every container passes only data relevant for the wrapped component.
Connecting more components to store
§
§
§
§
§
§
§22-connect-more-components
§
§
§
§
§
§
§
§
§
§
§
§
§
§
§
§
Checkout commits and click around, show some containers.
Middleware
§One of the greatest things about Redux is its modularity
§
§
§
•Logging
•Complex actions (Thunk, promise)
•devTools
•…
§
createStore(app, initialState, applyMiddleware(...middleware));
https://cdn-images-1.medium.com/max/800/1*5JaZSc3Jsn9PJY7daEDVDA.png
Redux-devtools
•All your actions and state visualized
•You can replay history
•Install chrome extension
•See kentico cloud or kiwi.com
§23-redux-devtools
DEMO
Checkout commit that has devtools and logger and showcase
Beware, once enabling redux-devtools for our demo project, you have to allow access to file URLs
for chrome extension, see:
https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/Troubleshooting.md
Redux-thunk
§Where to handle side-effects in Redux app?
§(async code (API communication), data generation like new Date() or Math.random())
•
•
•Components?
•Reducers?
•Action creators?
§à “thunk” action creators
•
§
Components – bad testability, too much logic in one place, which component…
Reducers – no way, those must be pure
Action creators – how, they only return plain object with action
Thunk actions
§“In computer programming, a thunk is a subroutine used to inject an additional calculation into
another subroutine. Thunks are primarily used to delay a calculation until it is needed, or to
insert operations at the beginning or end of the other subroutine.”
§ -- Wikipedia
§
§Function that can dispatch other actions:
§
§
§
§
export const saveItems = () =>
(dispatch, getState) => {
dispatch(savingStarted());
setTimeout(() => {
const items = JSON.stringify(getState().todoApp.itemsList.toJS());
localStorage.setItem('todoList', items);
dispatch(savingFinished());
}, 1000);
};
Explain code snippet:
-We do not have an API yet
-localStorage access is synchronous
-For demo purposes, we wrap it in setTimeout, so that it appears async and we can display the
loader
Saving items to localStorage
§Getting rid of Dummy TodoList container
§New component
•Displays saving status
•Watches for changes in todoList part of state
•On changes dispatches a thunk action to save items
•
üInstall redux-thunk
üDefine savingStarted & savingFinished action types and creators
üIntroduce reducer with saving flag
üCreate SavingStatus component
üWrap component in a container (list data & save callback)
§24-autosaving-component
DEMO:
Checkout the commits with SavingStatus component and explain what has been done
Data normalization
§
§Immutable.List-
§
§vs.
§
§Immutable.Map & Immutable.List
•
•
•
§Data should be stored in a normalized form (same as in relation DB)
üEasier manipulation – reducers (entity vs collection)
üNo duplication (for complex nested objects)
•
§
Image result for no png Image result for no png
Data normalization
§
{
itemsWithAuthors: [
{
id: '1',
title: 'Buy milk',
author: { id: '410237', name: 'Suzii' },
},
{
id: '2',
title: 'Learn Redux',
author: { id: '410237', name: 'Suzii' },
},
{
id: '3',
title: 'Be awesome',
author: { id: '325335', name: 'Slavo' },
},
],
};
{
authors: {
byId: {
'410237': { id: '410237', name: 'Suzii' },
'325335': { id: '325335', name: 'Slavo' },
},
},
items: {
allIds: ['1', '2', '3'],
byId: {
'1': {
id: '1',
title: 'Buy milk',
author: '410237',
},
'2': {
id: '2',
title: 'Learn redux',
author: '410237',
},
'3': {
id: '3',
title: 'Be awesome',
author: '325335',
},
},
},
}
Image result for no png Image result for no png
Explain the problem of duplicated data and how it can be solved.
Normalizing todo list
§We replace the itemsList with data structure:
§
§
§24-todo-list-normalization
{
items: {
allIds: [], // list of ids
byId: {}, // map of items indexed by id
}
}
Dame as in DB, interconnected just by ID, no data duplication.
However, if there is 1-n or 1-1 relationship, it might be sometimes easier to denormalize.
Memoization
§What do we pass to TodoList container?
§
§Two options:
•Both byId and allIds
•We create list of item in container and do not need to change the component at all
§
§
§But we are creating new instance of list every time mapStateToProps is called
àANY change in state,
àThe component is ALWAYS rerendered
à MEMOIZE
§25-todos-memoization
const getListOfItems = (items) => items.allIds.map(id => items.byId.get(id)).toList();
const getListOfItemsMemoized = memoizee(getListOfItems);
Unit testing
§Action creators:
•Very easy to test, however, most of the times unnecessary
•
§Thunk action creators:
•If you inject your dependencies à easy to test
•
§Reducers:
•Pure functions à super-easy to test
•
§MapStateToProps/Selectors (reselect library)
•Should be a pure function mapping data from store to another data structure à easy to test
§
The next lecture will handle unit testing so just simply mention how well testable a redux codebase
is.
How would you test all the data flow it you did not have Redux in your app?
-complex state modifications (reducers) or,
-data transformation (selectors/mapStateToProps) or,
-communication with API should be definitely tested.
Interesting libraries, concepts
§Redux is widely used in the community and there are tons of other packages that work with it.
§
§Integration with React: react-redux
§React router: react-router-redux
§Forms: redux-form
§Computing derived data: reselect
§Memoizing: memoizee
§Normalizing data from server: normalizr
§Middleware: redux-logger, redux-thunk
§
§And lots more…
§
Alternatives
§Flux
•"It is cool that you are inventing better Flux by not doing Flux at all.“ – reduxjs.org
•More stores, dispatcher entity, action handlers
•
§RePatch
•Redux with less boilerplate
§
§MobX
•Functional reactive programming
•
§Others
•There are new libraries every day
§
Sources
§http://redux.js.org
§https://css-tricks.com/learning-react-redux/
§https://code-cartoons.com/a-cartoon-intro-to-redux-3afb775501a6
§
§
§
§
Questions?
§zuzanad@kentico.com
§410237@mail.muni.cz
§