From 27bf77b34f92d34bcf61a9be2ade3f978f4d8ea1 Mon Sep 17 00:00:00 2001 From: Ken Date: Sun, 3 Mar 2019 09:31:59 -0800 Subject: [PATCH] swapped it back to react-redux --- package.json | 1 - step2-05/demo/README.md | 18 ++++- step2-06/demo/README.md | 81 ++++++++++--------- step2-06/demo/src/components/TodoFooter.tsx | 26 ++++-- step2-06/demo/src/components/TodoHeader.tsx | 25 ++++-- step2-06/demo/src/components/TodoList.tsx | 15 +++- step2-06/demo/src/components/TodoListItem.tsx | 19 ++++- step2-06/demo/src/index.tsx | 6 +- 8 files changed, 131 insertions(+), 60 deletions(-) diff --git a/package.json b/package.json index cda9618..6accdc7 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,6 @@ "redux": "^4.0.1", "redux-devtools-extension": "^2.13.8", "redux-starter-kit": "^0.4.3", - "redux-react-hook": "^3.2.0", "redux-thunk": "^2.3.0" } } diff --git a/step2-05/demo/README.md b/step2-05/demo/README.md index fcdf68c..bbf0501 100644 --- a/step2-05/demo/README.md +++ b/step2-05/demo/README.md @@ -5,12 +5,15 @@ In this step, we will look at solving the problems of complex application (as mentioned in Step 4) with a library called Redux. 1. Introduction to Redux -2. Creating the Redux store -3. Writing reducers -4. Dispatching actions +2. Why Use Redux? +3. Creating the Redux store +4. Writing reducers +5. Dispatching actions --- +## Introduction to Redux + As a reminder, the problem that we want to address are: 1. Data needs to be passed down from component to component via props. Even when some components do not need to know about some data. @@ -38,6 +41,15 @@ The [store](https://redux.js.org/basics/store) consists of a **state tree**, a * 3. **Reducers** are functions that take in the current state tree and an action, producing the next snapshot of the state tree. +## Why Use Redux? + +There are lots of alternatives available, but here are some really good reasons to go with Redux: + +1. For more complex applications, Flux pattern forces code to be written in a way that is easy to reason about +2. There maybe a need to serialize the application state to be transmitted across the wire somehow +3. Dev tooling is really amazing +4. Popularity of the framework means the ecosystem is mature at this point + # Creating the Redux store The [`createStore()`](https://redux.js.org/api/createstore) function is provided by Redux and can take in several arguments. The simplest form just takes in reducers. diff --git a/step2-06/demo/README.md b/step2-06/demo/README.md index cf88e4d..38e8dfa 100644 --- a/step2-06/demo/README.md +++ b/step2-06/demo/README.md @@ -2,61 +2,68 @@ [Lessons](../) | [Exercise](./exercise/) | [Demo](./demo/) -In this step, we will continue with Redux. Step 2.5 code can be used with any other web frameworks. This step, we will hook up the Redux store with React components. There are as many ways to bind Redux to React as number of stars in the sky. Being a tad bit opinionated in this bootcamp, we picked `react-redux-hooks` for its ease of use and the fact that it is another project from Facebook. You might want to investigate other packages, such as the [`react-redux`](https://react-redux.js.org/) project. +Redux is currently the most popular Flux implementation, and the ecosystem of related libraries has grown as a result. This is one of the reasons why it is a very popular library within Microsoft products. -We will demonstrate how to use `react-redux-hooks` to pass down the Redux store to the views: +Various GitHub users have collected "awesome lists" of tech and articles related to Redux. Here is [one such list](https://github.com/xgrommx/awesome-redux#react---a-javascript-library-for-building-user-interfaces), but it is literally impossible to list out all the related tech. -1. Provide the Store Context -2. Bind Redux store to Class Components -3. Bind Redux store to Functional Components +In this step, we will continue with Redux. Step 2.5 code can be used with any other web frameworks. This step, we will hook up the Redux store with React components. The official way to do this is with the the [`react-redux`](https://react-redux.js.org/) library. + +We will demonstrate how to use `react-redux` to pass down the Redux store to the views: + +1. Providing the Store to the Views +2. Mapping the Redux store to props ## Provide the Store Context Class Components will access the Redux store via the `StoreContext` from `react-redux-hooks`. In Step 2.4, you saw how the context is hooked up. So, instead of providing our own context for Redux store, we just take one that is already been created. We need to first hook up the `` component just like in Step 2.4. -```ts -const store = createStore(reducer, {}, composeWithDevTools()); +```js +const store = createStore(reducers); -ReactDOM.render( - - - , - document.getElementById('app') -); +const App = () => { + return ( + +
Hello World!
+
+ ); +}; ``` -## Bind Redux store to Class Components +## Mapping the Redux store to props -Any class component needs access to the Redux store, we would set the `contextType` property of that component: +`react-redux` exports the [`connect()`](https://react-redux.js.org/api/connect) function that maps portions of the state tree and dispatch functions into props in the child React component. Let's look at how that is done. -```ts -class TodoListItem extends React.Component { - render() { - const { todos } = this.context.getState(); - const dispatch = this.context.dispatch; +```js +import { connect } from 'react-redux'; - return (...); +const MyComponent = props => { + return
+ {props.prop1} + +
; +}; + +const ConnectedComponent = connect( + state => { + prop1: state.key1, + prop2: state.key2 + }, + dispatch => { + action1: (arg) => dispatch(actions.action1(arg)), + action2: (arg) => dispatch(actions.action2(arg)), } -} - -TodoListItem.contextType = StoreContext; +)(MyComponent); ``` -The `dispatch()` function as well as the state tree can be retrieved this way. `getState()` is returned so you can retrieve the current snapshot of the state tree. This simply uses the standard React _context_ API where the value is the store itself. +So, that's a lot to digest. We'll go through these different parts: -## Bind Redux store to Functional Components +1. First, the `` is simple component that expects to have props, without any knowledge of Redux. It is just a plain React Component. -Inside a functional component, we use some of the API provided by `react-redux-hooks` itself. To get specific or all of the state tree, we use the `useMappedState` hook: +2. The `connect()` function takes in several arguments. -```ts -const { todos } = useMappedState(state => { - todos: state.todos; -}); -``` + - The first argument maps portions of the Redux _state tree_ into `` _props_ + - The second arguments maps dispatch functions into `` _props_ -To dispatch actions, we need to retrieve this `dispatch()` function much the same way as the previous hook. We call `useDispatch()` hook: +3. Finally, `connect()` actually returns a function that **decorates** a `` into `` - it is a strange syntax, so do study it more closely here. -```ts -const dispatch = useDispatch(); -dispatch(action.addTodo('hello')); -``` +> Yes, `connect()` is a function that takes in functions as arguments that returns a function that takes in a component that return a component. Try to say this fast 10 times :) diff --git a/step2-06/demo/src/components/TodoFooter.tsx b/step2-06/demo/src/components/TodoFooter.tsx index d38e454..3d4f4fb 100644 --- a/step2-06/demo/src/components/TodoFooter.tsx +++ b/step2-06/demo/src/components/TodoFooter.tsx @@ -1,11 +1,16 @@ import React from 'react'; import { DefaultButton, Stack, Text } from 'office-ui-fabric-react'; import { actions } from '../actions'; -import { useMappedState, useDispatch } from 'redux-react-hook'; +import { connect } from 'react-redux'; +import { Store } from '../store'; -export const TodoFooter = () => { - const { todos } = useMappedState(state => state); - const dispatch = useDispatch(); +interface TodoFooterProps { + todos: Store['todos']; + clear: () => void; +} + +const TodoFooter = (props: TodoFooterProps) => { + const { todos, clear } = props; const itemCount = Object.keys(todos).filter(id => !todos[id].completed).length; @@ -14,7 +19,18 @@ export const TodoFooter = () => { {itemCount} item{itemCount === 1 ? '' : 's'} left - dispatch(actions.clear())}>Clear Completed + clear()}>Clear Completed ); }; + +const ConnectedTodoFooter = connect( + (state: Store) => ({ + todos: state.todos + }), + dispatch => ({ + clear: () => dispatch(actions.clear()) + }) +)(TodoFooter); + +export { ConnectedTodoFooter as TodoFooter }; diff --git a/step2-06/demo/src/components/TodoHeader.tsx b/step2-06/demo/src/components/TodoHeader.tsx index a272037..c0cc75e 100644 --- a/step2-06/demo/src/components/TodoHeader.tsx +++ b/step2-06/demo/src/components/TodoHeader.tsx @@ -2,14 +2,19 @@ import React from 'react'; import { Stack, Text, Pivot, PivotItem, TextField, PrimaryButton } from 'office-ui-fabric-react'; import { FilterTypes } from '../store'; import { actions } from '../actions'; -import { StoreContext } from 'redux-react-hook'; +import { connect } from 'react-redux'; + +interface TodoHeaderProps { + addTodo: (label: string) => void; + setFilter: (filter: FilterTypes) => void; +} interface TodoHeaderState { labelInput: string; } -export class TodoHeader extends React.Component<{}, TodoHeaderState> { - constructor(props: {}) { +class TodoHeader extends React.Component { + constructor(props: TodoHeaderProps) { super(props); this.state = { labelInput: undefined }; } @@ -49,7 +54,7 @@ export class TodoHeader extends React.Component<{}, TodoHeaderState> { } private onAdd = () => { - this.context.dispatch(actions.addTodo(this.state.labelInput)); + this.props.addTodo(this.state.labelInput); this.setState({ labelInput: undefined }); }; @@ -58,8 +63,16 @@ export class TodoHeader extends React.Component<{}, TodoHeaderState> { }; private onFilter = (item: PivotItem) => { - this.context.dispatch(actions.setFilter(item.props.headerText as FilterTypes)); + this.props.setFilter(item.props.headerText as FilterTypes); }; } -TodoHeader.contextType = StoreContext; +const ConnectedTodoHeader = connect( + state => {}, + dispatch => ({ + addTodo: label => dispatch(actions.addTodo(label)), + setFilter: filter => dispatch(actions.setFilter(filter)) + }) +)(TodoHeader); + +export { ConnectedTodoHeader as TodoHeader }; diff --git a/step2-06/demo/src/components/TodoList.tsx b/step2-06/demo/src/components/TodoList.tsx index b577baa..02cad3f 100644 --- a/step2-06/demo/src/components/TodoList.tsx +++ b/step2-06/demo/src/components/TodoList.tsx @@ -1,10 +1,16 @@ import React from 'react'; import { Stack } from 'office-ui-fabric-react'; import { TodoListItem } from './TodoListItem'; -import { useMappedState } from 'redux-react-hook'; +import { connect } from 'react-redux'; +import { Store } from '../store'; -export const TodoList = () => { - const { filter, todos } = useMappedState(state => state); +interface TodoListProps { + todos: Store['todos']; + filter: Store['filter']; +} + +const TodoList = (props: TodoListProps) => { + const { filter, todos } = props; const filteredTodos = Object.keys(todos).filter(id => { return filter === 'all' || (filter === 'completed' && todos[id].completed) || (filter === 'active' && !todos[id].completed); }); @@ -17,3 +23,6 @@ export const TodoList = () => { ); }; + +const ConnectedTodoList = connect((state: Store) => ({ ...state }))(TodoList); +export { ConnectedTodoList as TodoList }; diff --git a/step2-06/demo/src/components/TodoListItem.tsx b/step2-06/demo/src/components/TodoListItem.tsx index e105851..cd6cc74 100644 --- a/step2-06/demo/src/components/TodoListItem.tsx +++ b/step2-06/demo/src/components/TodoListItem.tsx @@ -2,9 +2,15 @@ import React from 'react'; import { Stack, Checkbox, IconButton, TextField, DefaultButton } from 'office-ui-fabric-react'; import { actions } from '../actions'; import { StoreContext } from 'redux-react-hook'; +import { Store } from '../store'; +import { connect } from 'react-redux'; interface TodoListItemProps { id: string; + todos: Store['todos']; + complete: (id: string) => void; + remove: (id: string) => void; + edit: (id: string, label: string) => void; } interface TodoListItemState { @@ -12,7 +18,7 @@ interface TodoListItemState { editLabel: string; } -export class TodoListItem extends React.Component { +class TodoListItem extends React.Component { constructor(props: TodoListItemProps) { super(props); this.state = { editing: false, editLabel: undefined }; @@ -75,4 +81,13 @@ export class TodoListItem extends React.Component ({ todos: state.todos }), + dispatch => ({ + complete: label => dispatch(actions.addTodo(label)), + remove: label => dispatch(actions.addTodo(label)), + edit: filter => dispatch(actions.setFilter(filter)) + }) +)(TodoListItem); + +export { ConnectedTodoListItem as TodoListItem }; diff --git a/step2-06/demo/src/index.tsx b/step2-06/demo/src/index.tsx index d994e52..ba2b58b 100644 --- a/step2-06/demo/src/index.tsx +++ b/step2-06/demo/src/index.tsx @@ -5,15 +5,15 @@ import { createStore } from 'redux'; import { TodoApp } from './components/TodoApp'; import { initializeIcons } from '@uifabric/icons'; import { composeWithDevTools } from 'redux-devtools-extension'; -import { StoreContext } from 'redux-react-hook'; +import { Provider } from 'react-redux'; const store = createStore(reducer, {}, composeWithDevTools()); initializeIcons(); ReactDOM.render( - + - , + , document.getElementById('app') );