More day 2 updates

This commit is contained in:
Elizabeth Craig
2019-02-28 21:26:06 -08:00
parent 24e0416935
commit 0d1160145a
5 changed files with 48 additions and 55 deletions

View File

@@ -1,4 +1,4 @@
# Step 2.5: Redux: Reducers (Demo)
# Step 2.5 - Redux: Reducers (Demo)
[Lessons](../) | [Exercise](./exercise/) | [Demo](./demo/)
@@ -12,9 +12,11 @@ f(data) => view
This is fine if the data never changes. However, React exists to deal with data updates via props (immutable) and state (changes based on `setState()` API). In the real world, data is shaped like a tree and view is shaped like a tree. They don't always match.
We can pass shared state data down to components using props and update it using callbacks, but as an application grows larger, passing around callbacks becomes unwieldy and the state data flow becomes hard to understand. To keep the application maintainable, we need a well-defined pattern for sharing and updating application state.
Facebook invented the [Flux](https://facebook.github.io/flux/) architectural pattern to solve this shared state issue. [Redux](https://redux.js.org/) is an implementation of Flux.
Redux is used inside many large, complex applications because of its clarity and predictability. It is really easy to debug and is easily extensible via its middleware architecture. In this lesson, we'll explore the heart of how Redux modifies state.
Redux is used inside many large, complex applications because of its clarity and predictability. It is easy to debug and easily extensible via its middleware architecture. In this lesson, we'll explore the heart of how Redux manages state.
Redux expects the data (store) to be a singleton state tree. It listens for messages to manipulate the state and passes updates down to views.
@@ -22,7 +24,7 @@ Redux expects the data (store) to be a singleton state tree. It listens for mess
### View
A view is a React components that consumes the store as its data. There is a special way Redux will map its data from the state tree into the different React components. The components will know to re-render when these bits of state are changed.
A view is a React component that consumes the store as its data. There is a special way Redux maps data from the state tree into the different React components. The components will know to re-render when these bits of state are changed.
### Action
@@ -38,7 +40,7 @@ There is a single dispatcher. It simply informs the store of all the actions tha
### Reducers
Redux uses **reducers** to manage state changes. This name comes from the "reducer" function passed to `Array.reduce()`.
Redux uses reducers to manage state changes. This name comes from the "reducer" function passed to `Array.reduce()`.
A Redux reducer is a simple stateless **pure function** (no side effects). Its only job is to change the state from one immutable snapshot to another. It takes state + an action message as input, makes a modified copy of the state based on the action message type and payload, and returns the new state. (Reducers should not modify the previous state.)
@@ -57,7 +59,7 @@ We won't be covering middleware much in these lessons since it's a more advanced
We begin the journey into Redux by looking at the store. The store consists of several parts:
1. **State/data** - We represent this both with an initial state and with a TypeScript interface.
2. **Reducers** - Responsible for reacting to action messages to change from a previous to the next state.
2. **Reducers** - Responsible for reacting to action messages to change from one state to the next.
3. **Dispatcher** - There should be only one dispatcher, which is exported by the store. We'll look at this in Step 6!
### Create store

View File

@@ -1,27 +1,29 @@
# Step 2.6
# Step 2.6 - Redux: Dispatching actions and examining state
[Lessons](../) | [Exercise](./exercise/) | [Demo](./demo/)
Redux: Dispatching Actions and Examining State.
In this step, we learn about the Redux methods `dispatch()` and `getState()`. Dispatching action messages to the store is the only means by which to instruct the reducers to modify the shared state tree.
In this step, we learn about `dispatch` and `getState()`. Dispatching action messages to the store is the only means by which to inform the reducers to modify the shared state tree.
We also see how to compose reducers to make up the complete state shape.
We also see how we may compose the reducers according to the shape.
## Dispatch
# Dispatch
As long as there is a store reference (we'll look at how to pass the store and the dispatch function into the view later), you can dispatch an action to trigger the middleware and reducers which in term changes the store which in turn will cause re-renders in the view.
Given a store reference, you can dispatch an action to trigger the middleware and reducers. This changes the store and causes the view to re-render. (We'll look at how to pass the store and the dispatch function into the view later.)
```ts
const store = createStore(redcuers);
const store = createStore(reducers);
store.dispatch(actions.addTodo('id0', 'hello world'));
```
> Note: It is important to note that dispatches in general have a "fire and forget" approach. We expect React to re-render the UI correct on its own accord. Rendering isn't necessarily synchronous in React! Chaining async action creators is a topic for Step 9
> Important note: Dispatches generally have a "fire and forget" approach. We expect React to re-render the UI correctly of its own accord. (Rendering isn't necessarily synchronous in React! Chaining async action creators is a topic for Step 9.)
# Reducers scoped to a portion of the state tree
## Reducers scoped to a portion of the state tree
In general, when an application grows so does the complexity of the state tree. In a Redux application, it is best to have reducers that deal with only a subportion of the tree. In our example, we have two parts of our state: `todos` and `filter`. We will split the reducer to pass the todos to a `todosReducer()` function and just return `all` to the `filter` key for now. This reducer organization helps in navigating the reducers because it matches the shape of the state one to one.
In general, when an application grows, so does the complexity of the state tree. In a Redux application, it is best to have reducers that deal with only a sub-portion of the tree.
In our example, we have two parts of our state: `todos` and `filter`. We will split the reducer to pass the todos to a `todosReducer()` function and just return `all` to the `filter` key for now. This organization helps in navigating and understanding the reducers because it matches the shape of the state one-to-one: there's a separate reducer for each key in state.
Compare this example which handles the whole state in one reducer...
```ts
// remember the shape of the store
@@ -35,7 +37,7 @@ In general, when an application grows so does the complexity of the state tree.
}
```
Compare this with the reducer organization
...to this one which splits it up.
```ts
function reducers(state, action) {
@@ -51,44 +53,38 @@ function reducers(state, action) {
}
```
In this way, it is very predictable which reducer changed the part of the state.
With the second example, it is easy to understand which reducer changed a given part of the state.
# `getState()`
## `getState()`
To examine the state of the store, you can call `getState()` to get the current snapshot of the state.
To examine the state of the store, you can call `store.getState()` to get a snapshot of the current state.
```ts
store.getState();
```
In general, you should only store serializable things in the state so that you can easily save or transfer it. You can even save this state into a browser's local storage and restore for the next boot of your application!
## Discussion
## Visualizing the reducer and store change
`getState()` will return a snapshot of the current state. The guidance is that we don't store anything other than serializable things here so that you can easily save it and transfer it. You can even save this state into a browser localstorage and restore for the next boot of your application!
If you want a really neat UI to show what the store looks when actions are dispatched to the store, use the [Redux DevTools extension](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd).
# Visualizing the Reducer and Store Change
If you want a really neat UI to show what the store looks when actions are dispatched to the store, use this Chrome extension:
https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd
The Chrome / Firefox Redux dev tools extension is a work of genius! It lets you replay actions and step backwards to debug the current state of Redux. In a large enough application, this kind of debuggability is invaluable. It also helps developers that are not immediately familiar with your application to quickly get a handle on how the state changes over some actions.
This extension (available for Chrome and Firefox) is a work of genius! It lets you replay actions and step backwards to debug the current state of a Redux application. In a large enough application, this kind of debuggability is invaluable. It also helps developers who are not familiar with your application to quickly get a handle on how the state changes in response to some actions.
# Exercise
If you still have `npm test` running from the previous step, stop it with `ctrl+C`. Start the app by running `npm start` from the root of the `frontend-bootcamp` folder. Click the "exercise" link under day 2 step 6 to see results.
## Visualize state changes with Chrome extension
## Visualize the state changes with Chrome extension
If you still have `npm test` running from the previous step, stop it with `ctrl+C`. Start the app by running `npm start` from the root of the `frontend-bootcamp` folder. Click the "exercise" link under day 2 step 6.
1. Install [Chrome extension](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd)
1. Install the [Redux DevTools Chrome extension](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd)
2. Hit F12, open the inspector panel entitled **Redux**
2. Hit F12 (`cmd+option+I` on Mac) and open the inspector panel entitled **Redux**
3. Modify `exercise/src/index.tsx` to dispatch actions (you're not limited to adding, you can also remove, and clear)
3. Modify `exercise/src/index.tsx` to dispatch actions (you're not limited to adding todos; you can also remove and clear)
4. Explore the actions' effects using the extension
## Playing with dispatching actions inside tests
1. open the `exercise/src/reducers/reducer.spec.ts`
Stop the app using `ctrl+C` and start the tests by running `npm test`.
1. Open `exercise/src/reducers/reducer.spec.ts`
2. Follow the instructions to fill out the reducer tests
3. Run the tests with `npm test`

View File

@@ -7,12 +7,10 @@ const store = createStore(reducer, {}, composeWithDevTools());
console.log(store.getState());
/*
TODO: dispatch several actions and see the effect to the state inside the Redux devtool
// TODO: dispatch several actions and see the effects on state inside the Redux devtools
store.dispatch(actions.???);
store.dispatch(actions.???);
store.dispatch(actions.???);
*/
// store.dispatch(actions.???);
// store.dispatch(actions.???);
// store.dispatch(actions.???);
console.log(store.getState());

View File

@@ -4,16 +4,15 @@ import { actions } from '../actions';
describe('reducers', () => {
it('should add items', () => {
// 1. use Redux's createStore() to create a store with reducer as argument
// along with the initial state
// 1. Use Redux's createStore() to create a store. Pass in a reducer along with the initial state.
//
// 2. call store.dispatch() with some action messages to indicate the kind of
// 2. Call store.dispatch() with some action messages to indicate the kind of
// action to perform (in this case, addTodo)
//
// 3. assert with expect() on the resultant store.getState().todos
// 3. Assert with expect() on the resultant store.getState().todos
});
// Tests left for you to do:
// Tests left for you to write:
// - remove
// - clear
// - complete

View File

@@ -2,9 +2,7 @@
[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.
If you still have `npm test` running from the last step, stop it using `ctrl+C`. Start the app 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 `<TodoApp>` with `<Provider>` as instructed in the comment
@@ -16,7 +14,7 @@ If you don't already have the app running, start it by running `npm start` from
5. do steps 2, 3, and 4 for the `TodoHeader.tsx` file
# Bonus Exercise
## Bonus Exercise
For further reading, go here to look up more information about the `mergeProps` and `options` parameters to `connect()`: