Redux Zuzana Dankovčíková “… predictable state container for JavaScript apps.” -- Redux docs 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. Problem 1: What is “root component” New feature request: → Displaying number of TODOs in the navigation bar? → “Unrelated” components dependent on the same data. → Lifting state up. But until when? How to make it scalable? Problem 2: Callbacks chain Save button TodoItemE dit TodoItem TodoList TodoApp Problem 2: Callbacks chain class TodoApp extends React.Component { // other methods // ... render() { return ( ); } } Save button TodoItemE dit TodoItem TodoList TodoApp 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." Building blocks Action • describes UI changes Store • receives action via dispatcher • calls root reducer Reducer • (prevState, action) => newState View • gets notified about state change • re-renders with new data 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 the user interactions that are possible in your app. redux-01-actions { type: 'TODO_APP_ITEM_CREATE', payload: { id: 42, text: 'Buy milk' } } const createItem = (text) => ({ type: TODO_APP_ITEM_CREATE, payload: { id: uuid(), text: text } }); Action creator - helper function for creating actionsAction - simple JS objects describing data change 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. Pure or impure? var count = 0; const increaseCount = (val) => count += val; const time = () => new Date().toLocaleTimeString(); const addFive = (val) => val + 5; const getMagicNumber = () => Math.random(); Reducers Previous state argument • Specify default value • Return same reference for irrelevant action type redux-02-reducers function counter(state = 0, action) { switch (action.type) { case 'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } } Reducer composition redux-02-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 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 redux-03-install-redux Moving state to the Redux store GOAL: No internal state in TodoApp.jsx ? How do we inject state to TodoApp component? ? How do we subscribe to changes? 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) redux-04-install-react-redux 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 → TodoApp.jsx – rendering number of items in navbar → TodoItem.jsx – if you want just one item to be editable at a time Is the state well encapsulated and local for the component? → It can stay in the stateful component. → TodoItemEdit.jsx – temporary value of the input field redux-05-eidtedItemId-reducer What about our props explosion? Performance Which components are re-rendered when we edit one todo item? → Whole app is re-rendered How to fix this? Connecting more components redux-06-connect-item Connecting more components to store redux-06-connect-item 3 Principles of Redux - recap 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." 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...) Drawbacks • Boilerplate & Verbosity -> have a look at Repatch • "One huge object" -> pretty much eliminated by reducer composition and ImmutableJS • "Component state vs Redux store" dillema -> see #1287 and: "Do whatever is less awkward." 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' }); Task git clone https://github.com/KenticoAcademy/PV247-2018.git cd PV247-2018 git checkout -b solution-1 redux-task-1 cd 05-redux npm install npm start Task 1. Implement removeTodo action a) Action type b) Action creator c) Handling in reducer d) Connect TodoItem 2. Implement # of todos in the navigation a) Component capable of rendering number b) Connect component and pass number of todos c) Render container component in app menu 3. [Bonus] Make sure only one item at a time can be editable a) You need to store editedItemId in store (todoApp) Redux vol 2. - advanced stuff • Normalization, memorization, selectors… • Optimizing performance • Async action - communicating with API • How to cleverly structure your state