diff --git a/step2-07/README.md b/step2-07/demo/README.md
similarity index 73%
rename from step2-07/README.md
rename to step2-07/demo/README.md
index 48aec50..fa55820 100644
--- a/step2-07/README.md
+++ b/step2-07/demo/README.md
@@ -1,4 +1,4 @@
-# Step 2.7
+# Step 2.7: Connect Redux Store to View (Demo)
[Lessons](../) | [Exercise](./exercise/) | [Demo](./demo/)
@@ -72,23 +72,3 @@ function mapDispatchToProps(dispatch) {
}
}
```
-
-# Exercise
-
-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 7 to see results.
-
-1. open up `exercise/src/index.tsx` and wrap `` with `` as instructed in the comment
-
-2. open up `exercise/src/components/TodoFooter.tsx` and erase the "nullable" type modifier (i.e. the ?) in the interface definition of `TodoFooterProps`
-
-3. Remove the `export` from `export const TodoFooter = (props: TodoFooterProps) => {`
-
-4. uncomment the bottom bits of code and fill in the implementation for `mapStateToProps()` and `mapDispatchToProps()` - feel free to use `TodoListItem.tsx` as a guide
-
-5. do steps 2, 3, and 4 for the `TodoHeader.tsx` file
-
-# Bonus Exercise
-
-For further reading, go here to look up more information about the `mergeProps` and `options` parameters to `connect()`:
-
-https://react-redux.js.org/api/connect
diff --git a/step2-07/demo/index.html b/step2-07/demo/index.html
index ab43ffe..e848070 100644
--- a/step2-07/demo/index.html
+++ b/step2-07/demo/index.html
@@ -4,7 +4,7 @@
-
+
diff --git a/step2-07/exercise/README.md b/step2-07/exercise/README.md
new file mode 100644
index 0000000..cfad75d
--- /dev/null
+++ b/step2-07/exercise/README.md
@@ -0,0 +1,23 @@
+# Step 2.7: Connect Redux Store to View (Exercise)
+
+[Lessons](../) | [Exercise](./exercise/) | [Demo](./demo/)
+
+# Exercise
+
+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 7 to see results.
+
+1. open up `exercise/src/index.tsx` and wrap `` with `` as instructed in the comment
+
+2. open up `exercise/src/components/TodoFooter.tsx` and erase the "nullable" type modifier (i.e. the ?) in the interface definition of `TodoFooterProps`
+
+3. Remove the `export` from `export const TodoFooter = (props: TodoFooterProps) => {`
+
+4. uncomment the bottom bits of code and fill in the implementation for `mapStateToProps()` and `mapDispatchToProps()` - feel free to use `TodoListItem.tsx` as a guide
+
+5. do steps 2, 3, and 4 for the `TodoHeader.tsx` file
+
+# Bonus Exercise
+
+For further reading, go here to look up more information about the `mergeProps` and `options` parameters to `connect()`:
+
+https://react-redux.js.org/api/connect
diff --git a/step2-07/exercise/index.html b/step2-07/exercise/index.html
index ab43ffe..ee7d10d 100644
--- a/step2-07/exercise/index.html
+++ b/step2-07/exercise/index.html
@@ -4,7 +4,7 @@
-
+
diff --git a/step2-08/README.md b/step2-08/demo/README.md
similarity index 82%
rename from step2-08/README.md
rename to step2-08/demo/README.md
index a87e1fc..7c098c6 100644
--- a/step2-08/README.md
+++ b/step2-08/demo/README.md
@@ -1,4 +1,4 @@
-# Step 2.8
+# Step 2.8: Reduce Boilerplate (Demo)
[Lessons](../) | [Exercise](./exercise/) | [Demo](./demo/)
@@ -143,25 +143,3 @@ 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.
-
-# Exercise
-
-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!!
-
-1. open up `exercise/src/reducers/index.ts`
-
-2. rewrite the reducer functions `todoReducers`, `filterReducers` with the help of `createReducer()`
-
-3. rewrite the `reducer()` function with `combineReducer()`
-
-4. open up `exercise/src/reducers/pureFunctions.ts`
-
-5. rewrite all the reducers related to the todos by following instructions
-
-# Further reading
-
-- 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
diff --git a/step2-08/demo/index.html b/step2-08/demo/index.html
index ab43ffe..e848070 100644
--- a/step2-08/demo/index.html
+++ b/step2-08/demo/index.html
@@ -4,7 +4,7 @@
-
+
diff --git a/step2-08/exercise/README.md b/step2-08/exercise/README.md
new file mode 100644
index 0000000..a5ea850
--- /dev/null
+++ b/step2-08/exercise/README.md
@@ -0,0 +1,25 @@
+# Step 2.8: Reduce Boilerplate (Exercise)
+
+[Lessons](../) | [Exercise](./exercise/) | [Demo](./demo/)
+
+# Exercise
+
+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!!
+
+1. open up `exercise/src/reducers/index.ts`
+
+2. rewrite the reducer functions `todoReducers`, `filterReducers` with the help of `createReducer()`
+
+3. rewrite the `reducer()` function with `combineReducer()`
+
+4. open up `exercise/src/reducers/pureFunctions.ts`
+
+5. rewrite all the reducers related to the todos by following instructions
+
+# Further reading
+
+- 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
diff --git a/step2-08/exercise/index.html b/step2-08/exercise/index.html
index ab43ffe..ee7d10d 100644
--- a/step2-08/exercise/index.html
+++ b/step2-08/exercise/index.html
@@ -4,7 +4,7 @@
-
+
diff --git a/step2-09/README.md b/step2-09/demo/README.md
similarity index 98%
rename from step2-09/README.md
rename to step2-09/demo/README.md
index 0dec6c6..23330b0 100644
--- a/step2-09/README.md
+++ b/step2-09/demo/README.md
@@ -1,4 +1,4 @@
-# Step 2.9
+# Step 2.9: Service Calls (Demo)
[Lessons](../) | [Exercise](./exercise/) | [Demo](./demo/)
diff --git a/step2-09/demo/index.html b/step2-09/demo/index.html
index ab43ffe..e848070 100644
--- a/step2-09/demo/index.html
+++ b/step2-09/demo/index.html
@@ -4,7 +4,7 @@
-
+
diff --git a/step2-09/exercise/README.md b/step2-09/exercise/README.md
new file mode 100644
index 0000000..1a692e6
--- /dev/null
+++ b/step2-09/exercise/README.md
@@ -0,0 +1,62 @@
+# Step 2.9: Service Calls (Exercise)
+
+[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
+
+# `redux-thunk`: side effects inside action creators
+
+Redux Thunk middleware for actions with service calls. The documentation is here:
+
+https://github.com/reduxjs/redux-thunk
+
+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 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. To
+
+# 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
+
+```ts
+function addTodo(label: string) {
+ return { type: 'addTodo', id: uuid(), label };
+}
+```
+
+In order for us to make service calls, we need to super charge this with the power of `redux-thunk`
+
+```ts
+function addTodo(label: string) {
+ return async (dispatch: any, getState: () => Store) => {
+ const addAction = actions.addTodo(label);
+ const id = addAction.id;
+ dispatch(addAction);
+ await service.add(id, getState().todos[id]);
+ };
+}
+```
+
+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()`
+
+# Exercise
+
+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
+
+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
+
+3. open `exercise/src/index.tsx` and follow the instructions in the TODO comment to make the app prepopulate with data from the service.
diff --git a/step2-09/exercise/index.html b/step2-09/exercise/index.html
index ab43ffe..ee7d10d 100644
--- a/step2-09/exercise/index.html
+++ b/step2-09/exercise/index.html
@@ -4,7 +4,7 @@
-
+