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 §