From 8e4b697ba8dbf42257d7fd6781465a52b013f040 Mon Sep 17 00:00:00 2001 From: Elizabeth Craig Date: Fri, 1 Mar 2019 12:41:44 -0800 Subject: [PATCH] Day 2 steps 7-9 updates --- step2-07/demo/README.md | 35 ++++++++++++++++++++++++++------ step2-08/demo/README.md | 40 +++++++++++++++++++------------------ step2-08/exercise/README.md | 18 ++++++++--------- step2-09/demo/README.md | 32 ++++++++++++++--------------- step2-09/exercise/README.md | 11 +++++----- 5 files changed, 79 insertions(+), 57 deletions(-) diff --git a/step2-07/demo/README.md b/step2-07/demo/README.md index 61dce33..6404041 100644 --- a/step2-07/demo/README.md +++ b/step2-07/demo/README.md @@ -47,24 +47,47 @@ const NewComponent = connect( `connect()` takes in a few functions that map portions of the state tree and dispatcher functions into props. It is a **higher-order function**, meaning that the return value of `connect()` is a function that decorates `OldComponent` into a `NewComponent` with all the mapped props. -A [`mapStateToProps`](https://react-redux.js.org/api/connect#mapstatetoprops-state-ownprops-object) function selects out portions of the state tree. This function informs the connected view when to re-render based on a shallow comparison from previous state. +#### `mapStateToProps` + +A [`mapStateToProps`](https://react-redux.js.org/api/connect#mapstatetoprops-state-ownprops-object) function returns portions of the state tree that will be passed to the component as props. + +Under the hood, this function helps inform the connected view when it should re-render based on a shallow comparison with the previous state. ```ts -function mapStateToProps(state) { +interface Store { + foo: string; + // and probably some other properties +} + +interface FooProps { + foo: string; + bar: string; +} + +function mapStateToProps(state: Store): Partial { return { foo: state.foo }; } ``` -An optional [`mapDispatchToProps`](https://react-redux.js.org/api/connect#mapdispatchtoprops-object-dispatch-ownprops-object) function will trigger the action message dispatch mechanism of Redux. It looks like this: +#### `mapDispatchToProps` + +A [`mapDispatchToProps`](https://react-redux.js.org/api/connect#mapdispatchtoprops-object-dispatch-ownprops-object) function generates props which are used to dispatch Redux actions. + +This function generally returns props which the component will use as callbacks in response to user actions. ```ts -function mapDispatchToProps(dispatch) { +interface FooProps { + foo: string; + addTodo: (label: string) => void; +} + +function mapDispatchToProps(dispatch: any): Partial { return { // the dispatched message COULD be generated by an - // action creator instead - addTodo: () => dispatch({ type: 'addTodo', ... }) + // action creator instead (see later steps) + addTodo: (label: string) => dispatch({ type: 'addTodo', label }) } } ``` diff --git a/step2-08/demo/README.md b/step2-08/demo/README.md index 7c098c6..9b32cd9 100644 --- a/step2-08/demo/README.md +++ b/step2-08/demo/README.md @@ -9,22 +9,22 @@ At this point, you might asking why am I adding so much boilerplate code? -A lot of code seems to be repeated with Redux. Redux is very much function based and has a lot of opportunites for some refactoring to make it less boilerplate'ish. +A lot of code seems to be repeated with Redux. Redux is very much function-based and has a lot of opportunities for some refactoring to make it less boilerplate-heavy. -I argue that part of the boilerplate is just turning what would otherwise by implicit to be explicit. This is GOOD in a large applications so that there is no magic. +I argue that part of the boilerplate is just making things explicit that would otherwise be implicit. This is GOOD in a large application so that there is no magic. -However, I argue for two things to make things much better: +However, I argue that there are two major areas for improvement: -1. writing against immutable data structures is hard -2. the switch statements is cumbersome and error prone (e.g. with default case missing) +1. Writing against immutable data structures is hard +2. The switch statements are cumbersome and error-prone (e.g. with default case missing) -# `redux-starter-kit`: A simple batteries-included toolset to make using Redux easier +## `redux-starter-kit`: A simple batteries-included toolset to make using Redux easier -Introducing an official library from Redux team that makes this much better. We'll start with `createReducer()` +Introducing [`redux-starter-kit`](https://redux-starter-kit.js.org/), an official helper library from Redux team, makes this much better. We'll start with `createReducer()`. -## `createReducer()`: takes away the switch statement +### `createReducer()`: takes away the switch statement -`createReducers()` simplifies things a lot! The best way illustrate what it does is with some code: +[`createReducer()`](https://redux-starter-kit.js.org/api/createreducer) simplifies things a lot! The best way illustrate what it does is with some code. Previously, we'd write our reducer like this: ```ts function todoReducer(state, action) { @@ -46,10 +46,10 @@ function todoReducer(state, action) { } ``` -can be rewritten as: +We can rewrite this with `redux-starter-kit` as follows: ```ts -import {createReducer} from 'redux-starter-kit'; +import { createReducer } from 'redux-starter-kit'; const todoReducer = createReducer({}, { addTodo: (state, action) => ..., @@ -61,9 +61,9 @@ const todoReducer = createReducer({}, { Several important features of `createReducer()`: -1. it allows a more concise way of writing reducers with keys that match the `action.type` (using convention) +1. Provides a more concise way of writing reducers, using an object with keys that match the possible values of `action.type` -2. handles "no match" case and returns the previous state (rather than a blank state like we had done previously) +2. Handles "no match" case and returns the previous state (rather than a blank state like we had done previously) 3. it incorporates a library called [`immer`](https://github.com/mweststrate/immer#reducer-example) that allows us to write code that mutates a draft object and ultimately copies over the old snapshot with the new. Instead of writing immutable data manipulation: @@ -78,7 +78,7 @@ function removeItem(array, action) { } ``` -Can become code that we write with mutable arrays (without spread syntax): +We can write code with mutable arrays (without spread syntax): ```ts function insertItem(array, action) { @@ -91,7 +91,7 @@ function removeItem(array, action) { } ``` -There are cases where you need to replace the entire state at a time (like the `setFilter`). Simply returning a new value without modifying the state like so: +In cases where you need to replace the entire state (like `setFilter`), simply return a new value without modifying the state like so: ```ts function setFilter(state, action) { @@ -99,9 +99,11 @@ function setFilter(state, action) { } ``` -## `combineReducer()` - combining reducers +### `combineReducers()` - combining reducers -This another is demonstration of how to write reducers with less boilerplate code. We can use a built-in `redux` function to combineReducers. Application state shape grows usually be splitting the store. Our Redux store so far has this shape, roughly: +Using [`combineReducers()`](https://redux.js.org/recipes/structuring-reducers/using-combinereducers), we can further reduce the amount of boilerplate code. As the application store evolves and is responsible for increasing amounts of state, it becomes advantageous to decompose the reducer into smaller functions. `combineReducers()` provides an API that lets authors build more, smaller reducers, each with a single responsibility. + +Our todo app's Redux store so far has this shape, roughly: ```js const state = { @@ -120,7 +122,7 @@ const state = { }; ``` -Currently, the store captures two separate but related ideas: the todo items and the selected filter. The reducers should follow the shape of the store. Think of reducers as part of the store itself and are responsible to update a single part of the store based on actions that they receive as a second argument. As complexity of state grows, we split these reducers: +Currently, the store captures two separate but related pieces of data: the todo items and the selected filter. The reducers should follow the shape of the store. Think of reducers as parts of the store which are responsible to update a single part of the store based on the action passed in. As complexity of state grows, we split these reducers: ```ts // from last step, using createReducer @@ -142,4 +144,4 @@ export const reducer = combineReducers({ }); ``` -`combineReducers` handles the grunt-work of sending _actions_ to each combined reducer. Therefore, when an action arrives, each reducer is given the opportunity to modify its own state tree based on the incoming action. +`combineReducers` handles the grunt work of sending actions to the appropriate reducer. Therefore, when an action arrives, each reducer is given the opportunity to modify its own section of the state tree based on the incoming action. diff --git a/step2-08/exercise/README.md b/step2-08/exercise/README.md index 7f8161a..c51ba5a 100644 --- a/step2-08/exercise/README.md +++ b/step2-08/exercise/README.md @@ -4,20 +4,20 @@ If you don't already have the app running, start it by running `npm start` from the root of the `frontend-bootcamp` folder. Click the "exercise" link under day 2 step 8 to see results. -> Hint! This section is tricky, so all the solution is inside "demo" as usual. Feel free to copy & paste if you get stuck!! +> Hint! This section is tricky, so the whole solution is inside `demo` as usual. Feel free to copy & paste if you get stuck!! -1. open up `exercise/src/reducers/index.ts` +1. Open up `exercise/src/reducers/index.ts` -2. rewrite the reducer functions `todoReducers`, `filterReducers` with the help of `createReducer()` +2. Rewrite the reducer functions `todoReducers` and `filterReducers` with the help of [`createReducer()`](https://redux-starter-kit.js.org/api/createreducer) -3. rewrite the `reducer()` function with `combineReducer()` +3. Rewrite the `reducer()` function with [`combineReducers()`](https://redux.js.org/recipes/structuring-reducers/using-combinereducers) -4. open up `exercise/src/reducers/pureFunctions.ts` +4. Open up `exercise/src/reducers/pureFunctions.ts` -5. rewrite all the reducers related to the todos by following instructions +5. Rewrite all the reducers related to the todos by following the instructions in the code -# Further reading +## Further reading -- immer: https://github.com/mweststrate/immer - improves ergonomics of working with immutables by introducing the concept of mutating a draft +- [`immer`](https://github.com/mweststrate/immer) - Improves ergonomics of working with immutables by introducing the concept of mutating a draft -- redux-starter-kit: https://github.com/reduxjs/redux-starter-kit - help address common concerns of Redux in boilerplate and complexity +- [`redux-starter-kit`](https://github.com/reduxjs/redux-starter-kit) - Help address common concerns of Redux in boilerplate and complexity diff --git a/step2-09/demo/README.md b/step2-09/demo/README.md index b0a5019..6ab57b7 100644 --- a/step2-09/demo/README.md +++ b/step2-09/demo/README.md @@ -1,26 +1,24 @@ -# Step 2.9: Service Calls (Demo) +# Step 2.9: Service calls (Demo) [Lessons](../) | [Exercise](./exercise/) | [Demo](./demo/) -> Note: this step doesn't work with the live site on github.io. Clone this repo to try this step out +> Note: this step doesn't work with the live site on github.io. Clone the repo to try this step out. -# `redux-thunk`: side effects inside action creators +## `redux-thunk`: side effects inside action creators -Redux Thunk middleware for actions with service calls. The documentation is here: +The [Redux Thunk](https://github.com/reduxjs/redux-thunk) middleware allows writing actions that make service calls. -https://github.com/reduxjs/redux-thunk +Remember those simple little action functions? They're called action creators. These little functions can be charged with superpowers to allow asynchronous side effects to happen while creating the messages. Asynchronous side effects include service calls against APIs. -Remember those simple little action functions? They're called action creators. These little functions can be charged with super powers to allow asynchronous side effects to happen while creating the messages. Asynchronous side effects include service calls against APIs. +Action creators are a natural place to put service calls. Redux Thunk middleware passes `dispatch()` and `getState()` from the store into the action creators. This allows the action creator itself to dispatch different actions in between async side effects. Combined with the async / await syntax, coding service calls is a cinch! -Action creators are a natural place to put service calls. Redux thunk middleware passes in the `dispatch()` and `getState()` from the store into the action creators. This allows the action creator itself to dispatch different actions in between async side effects. Combined with the async / await syntax, coding service calls is a cinch! +Most of the time, in a single-page app, we apply **optimistic UI updates**. We can update the UI before the network call completes so the UI feels more responsive. -Most of the time, in a single-page app, we apply **optimistic UI updates**. We can update the UI before the network call completes so the UI feels more responsive. To - -# Action Creator with a Thunk +## Action creator with a thunk [What's a thunk?](https://daveceddia.com/what-is-a-thunk/) - it is a wrapper function that returns a function. What does it do? Let's find out! -This action creator just returns an object +This action creator just returns an object: ```ts function addTodo(label: string) { @@ -28,7 +26,7 @@ function addTodo(label: string) { } ``` -In order for us to make service calls, we need to super charge this with the power of `redux-thunk` +In order for us to make service calls, we need to supercharge this with the power of `redux-thunk` ```ts function addTodo(label: string) { @@ -43,8 +41,8 @@ function addTodo(label: string) { Let's make some observations: -1. the outer function has the same function signature as the previous one -2. it returns a function that has `dispatch` and `getState` as parameters -3. the inner function is `async` enabled, and can await on "side effects" like asynchronous service calls -4. this inner function has the ability to dispatch additional actions because it has been passed the `dispatch()` function from the store -5. this inner function also has access to the state tree via `getState()` +1. The outer function has the same function signature as the previous one +2. It returns a function that has `dispatch` and `getState` as parameters +3. The inner function is `async` enabled, and can await on "side effects" like asynchronous service calls +4. This inner function has the ability to dispatch additional actions because it has been passed the `dispatch()` function from the store +5. This inner function also has access to the state tree via `getState()` diff --git a/step2-09/exercise/README.md b/step2-09/exercise/README.md index 5128fe9..7c23822 100644 --- a/step2-09/exercise/README.md +++ b/step2-09/exercise/README.md @@ -2,14 +2,13 @@ [Lessons](../) | [Exercise](./exercise/) | [Demo](./demo/) -> Note: this step doesn't work with the live site on github.io. Clone this repo to try this step out +> Note: this step doesn't work with the live site on github.io. Clone the repo to try this step out. If you don't already have the app running, start it by running `npm start` from the root of the `frontend-bootcamp` folder. Click the "exercise" link under day 2 step 9 to see results. -1. open up `exercise/src/service/index.ts` and study the signature of the functions to call the service such as the `add()` function +1. Open `exercise/src/service/index.ts` and study the signature of the functions to call the service, such as the `add()` function -2. open `exercise/src/actions/index.ts` and fill in the missing content inside `actionsWithService` +2. Open `exercise/src/actions/index.ts` and fill in the missing content inside `actionsWithService` + - Note that the `complete` and `clear` functions require you to write your own wrapper function -- note that the `complete` and `clear` functions require you to write your own wrapper function - -3. open `exercise/src/index.tsx` and follow the instructions in the TODO comment to make the app prepopulate with data from the service. +3. Open `exercise/src/index.tsx` and follow the instructions in the TODO comment to make the app pre-populate with data from the service