# React & Redux: Lesson3 Redux MiddleWare ###### tags: `Recat2` [ToC] # Introduction {%youtube 9e3divgVWus%} {%youtube TFzVN0hDcpI%} [Here's the commit with the changes made in this video.](https://github.com/udacity/reactnd-redux-todos-goals/commit/f9aed03cd19975c8aa96cd25f497cba955fb8d78) # Redux Middleware {%youtube WOEUwVOI0iw%} You’ve learned how Redux makes state management more predictable: in order to change the store’s state, an action describing that change must be dispatched to the reducer. In turn, the reducer produces the new state. This new state replaces the previous state in the store. So the next time `store.getState()` is called, the new, most up-to-date state is returned. Between the dispatching of an action and the reducer running, we can introduce code called **middleware** to intercept the action before the reducer is invoked. The [Redux docs](http://redux.js.org/docs/advanced/Middleware.html) describe middleware as: > …a third-party extension point between dispatching an action, and the moment it reaches the reducer. What's great about middleware is that once it receives the action, it can carry out a number of operations, including: - producing a side effect (e.g., logging information about the store) - processing the action itself (e.g., making an asynchronous HTTP request) - redirecting the action (e.g., to another piece of middleware) - dispatching supplementary actions …or even some combination of the above! Middleware can do any of these before passing the action along to the reducer. Let's replace our `checkAndDispatch()` function with a real Redux middleware function. {%youtube 2Dx7OBAEhV0%} [Here's the commit with the changes made in this video.](https://github.com/udacity/reactnd-redux-todos-goals/commit/e1e96d97cd1566a35a34623fc5b43d1a0ba5d3ef) ## Where Middleware Fits The way we had to structure our code originally, our `checkAndDispatch()` function had to run before `store.dispatch()`. Why is this? Because when `store.dispatch()` is invoked, it immediately calls the reducer that was passed in when `createStore()` was invoked. If you remember back to the first lesson, this is what our `dispatch()` function looked like (and is very similar to the real Redux `dispatch()` function): ```javascript const dispatch = (action) => { state = reducer(state, action) listeners.forEach((listener) => listener()) } ``` So you can see that calling `store.dispatch()` will immediately invoke the `reducer()` function. There's no way to run anything in between the two function calls. So that's why we had to make our `checkAndDispatch()` so that we can run verification code before calling `store.dispatch()`. However, this isn't maintainable. If we wanted to add another check, then we'd need to write another preceding function, that then calls `checkAndDispatch()` that then calls `store.dispatch()`. Not maintainable at all. With Redux's middleware feature, we can run code between the call to `store.dispatch()` and reducer(). The reason this works, is because Redux's version of dispatch() is a bit more sophisticated than ours was, and because we provide the middleware functions when we create the store. ```javascript const store = Redux.createStore( <reducer-function>, <middleware-functions> ) ``` Redux's `createStore()` method takes the reducer function as its first argument, but then it can take a second argument of the middleware functions to run. Because we set up the Redux store with knowledge of the middleware function, it runs the middleware function between `store.dispatch()` and the invocation of the reducer. ## Applying Middleware Just as we saw in the previous video, we can implement middleware into a Redux app by passing it in when creating the store. More specifically, we can pass in the `applyMiddleware()` function as an optional argument into createStore(). Here's `applyMiddleware()`'s signature: ```javascript applyMiddleware(...middlewares) ``` Note the spread operator on the `middlewares` parameter. This means that we can pass in as many different middleware as we want! Middleware is called in the order in which they were provided to `applyMiddleware()`. We currently have the `checker` middleware applied to our app, but we'll soon add a new `logger` middleware as well. To create a Redux store that uses our `checker` middleware, we can do the following: ```javascript const store = Redux.createStore(rootReducer, Redux.applyMiddleware(checker)) ``` ## A New Middleware: Logging Currently, our application is making use of a single middleware: `checker`. Because we can use multiple middleware functions in a single application, let's create a new middleware function called `logger` that will log out information about the state and action. The benefits of this `logger()` middleware function are huge while developing the application. We'll use this middleware to intercept all dispatch calls and log out what the action is that's being dispatched and what the state changes to after the reducer has run. Being able to see this kind of information will be immensely helpful while we're developing our app. We can use this info to help us know what's going on in our app and to help us track down any pesky bugs that creep in. {%youtube GWrRJOTCfI8%} [Here's the commit with the changes made in this video.](https://github.com/udacity/reactnd-redux-todos-goals/commit/e07e1785e0bf90b2128eac3a63674ec9c8daabac) ## Summary In this section, we looked at using middleware. According to the Redux docs: > Middleware is the suggested way to extend Redux with custom functionality. Middleware is added to the Redux store using `Redux.applyMiddleware()`. You can only add middleware when you initially create the store: ```javascript const store = Redux.createStore( <reducer-function>, Redux.applyMiddleware(<middleware-functions>) ) ``` ## Further Research The following might be a bit advanced at this point, but give them a quick read through right now and definitely bookmark them to come back and read later: - [Middleware Docs](https://redux.js.org/advanced/middleware) - [API for Redux's Middleware](https://redux.js.org/api-reference/applymiddleware) > shannon note ```javascript //middleWare: before get into dispach //cheker input store, then return next, and next return action, and action return void //如果你有很多middleware就可以比較maintainable因為next之後繼續next直到只剩下action return void const checker = (store) => (next) => (action) =>{ if(action.type === ADD_TODO && action.todo.name.toLowerCase().includes('bitcoin')){ return alert("Nope. That's a bad idea."); } if(action.type === ADD_GOAL && action.goal.name.toLowerCase().includes('bitcoin')){ return alert("Nope. That's a bad idea."); } return next(action); } ``` # Lesson Summary {%youtube NqVZhcgmZyw%} ## Lesson Challenge Answer the following questions and share your answers with your classmates: 1) What is Redux middleware and what are some use cases for it? 2) What is the relationship between middleware and the store? # 完整程式碼 ```javascript= <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Todos and goals</title> <script src='https://cdnjs.cloudflare.com/ajax/libs/redux/3.7.2/redux.min.js'></script> </head> <body> <div> <h1>TODO List</h1> <input id ='todo' type='text' placeholder="Add TODO" /> <button id='todoBtn'>Add Todo</button> <ul id='todos'></ul> </div> <div> <h1>Goals</h1> <input id='goal' type='text' placeholder='Add Goal' /> <button id='goalBtn'>Add Goal</button> <ul id='goals'></ul> </div> <script type="text/javascript"> //random id function generateId(){ return Math.random().toString(36).substring(2) + (new Date()).getTime().toString(36); } // App Code const ADD_TODO = 'ADD_TODO' const REMOVE_TODO = 'REMOVE_TODO' const TOGGLE_TODO = 'TOGGLE_TODO' const ADD_GOAL = 'ADD_GOAL' const REMOVE_GOAL = 'REMOVE_GOAL' //觸發動作的key function addTodoAction(todo) { return { type: ADD_TODO, todo, } } function removeTodoAction(id) { return { type: REMOVE_TODO, id, } } function toggleTodoAction(id) { return { type: TOGGLE_TODO, id, } } function addGoalAction(goal) { return { type: ADD_GOAL, goal, } } function removeGoalAction(id) { return { type: REMOVE_GOAL, id, } } //reducers : 所有動作應該時做的事情,每次dispatch(action)就會去lib找要做哪些動作ㄣ function todos(state = [], action) { switch (action.type) { case ADD_TODO: return state.concat([action.todo]) // 合併兩個或多個陣列。此方法不會改變現有的陣列 case REMOVE_TODO: return state.filter((todo) => todo.id !== action.id) case TOGGLE_TODO: //done todo return state.map((todo) => todo.id !== action.id ? todo : Object.assign({}, todo, { complete: !todo.complete })) //{} 被後面的 todo 和 {complete:...} 給覆蓋 如果重複的話 default: return state } } function goals(state = [], action) { switch (action.type) { case ADD_GOAL: return state.concat([action.goal]) case REMOVE_GOAL: return state.filter((goal) => goal.id !== action.id) default: return state } } //-------middleWare: before get into dispach-------// //cheker input store, then return next, and next return action, and action return void //如果你有很多middleware就可以比較maintainable因為next之後繼續next直到只剩下action return void const checker = (store) => (next) => (action) =>{ if(action.type === ADD_TODO && action.todo.name.toLowerCase().includes('bitcoin')){ return alert("Nope. That's a bad idea."); } if(action.type === ADD_GOAL && action.goal.name.toLowerCase().includes('bitcoin')){ return alert("Nope. That's a bad idea."); } return next(action); } const logger = (store) => (next) => (action) =>{ console.group(action.type); console.log('The action: ', action); const result = next(action); console.log('The new state: ', store.getState()) console.groupEnd(); return result; } // This is combineReducers Do // function app(state = {}, action) { // return { // todos: todos(state.todos, action), // goals: goals(state.goals, action), // } // } //-------將reducer的集合 以及 middleware放進去store裡面-------// const store = Redux.createStore(Redux.combineReducers({ todos, goals }), Redux.applyMiddleware(checker, logger)) //-------每次state改變的時候就會呼叫一次-------// store.subscribe(() => { // console.log('The new state is: ', store.getState()) const {todos, goals} = store.getState(); document.getElementById('goals').innerHTML = ''; document.getElementById('todos').innerHTML = ''; todos.forEach(addTodoToDom); goals.forEach(addGoalToDom); }) //-------畫面上的觸發動作-------// document.getElementById('todoBtn').addEventListener('click', addTodo); document.getElementById('goalBtn').addEventListener('click', addGoal); function addTodo(){ //取得input value const input = document.getElementById('todo'); const name = input.value; input.value=''; //clear input //將動作放入store store會經過reducer store.dispatch(addTodoAction({ name, complete: false, id: generateId() })) } function addGoal(){ const input = document.getElementById('goal'); const name = input.value; input.value=''; store.dispatch(addGoalAction({ name, id: generateId() })) } //-------新增物件並且呈現在畫面上-------// function createRemoveButton(onClick){ const removeBtn = document.createElement('button'); removeBtn.innerHTML = 'X'; removeBtn.addEventListener('click', onClick); return removeBtn; } function addTodoToDom(todo){ const node = document.createElement('li'); const name = document.createTextNode(todo.name); const removeBtn = createRemoveButton(()=>{ store.dispatch(removeTodoAction(todo.id)); }) node.appendChild(removeBtn); node.appendChild(name); //每次新增節點的時候就添加listener屬性 //有劃線的效果 complete part: css text-decoration: line-through 刪除線 node.style.textDecoration = todo.complete ? 'line-through' : 'none'; //event listeners when click the todo item node.addEventListener('click', () => { store.dispatch(toggleTodoAction(todo.id)) }) //一切都設置好之後再加到dom裡面 document.getElementById('todos').appendChild(node); } function addGoalToDom(goal){ const node = document.createElement('li'); const name = document.createTextNode(goal.name); const removeBtn = createRemoveButton(()=>{ store.dispatch(removeGoalAction(goal.id)) }) node.appendChild(removeBtn); node.appendChild(name); document.getElementById('goals').appendChild(node); } </script> </body> </html> ```