swapped it back to react-redux

This commit is contained in:
Ken
2019-03-03 09:31:59 -08:00
parent 3eb97ab85a
commit 27bf77b34f
8 changed files with 131 additions and 60 deletions

View File

@@ -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"
}
}

View File

@@ -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.

View File

@@ -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 `<StoreContext.Provider>` component just like in Step 2.4.
```ts
const store = createStore(reducer, {}, composeWithDevTools());
```js
const store = createStore(reducers);
ReactDOM.render(
<StoreContext.Provider value={store}>
<TodoApp />
</StoreContext.Provider>,
document.getElementById('app')
const App = () => {
return (
<Provider store={store}>
<div>Hello World!</div>
</Provider>
);
};
```
## 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 <div>
{props.prop1}
<button onClick={props.action1()}>Click Me</button>
</div>;
};
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 `<MyComponent>` 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 `<MyComponent>` _props_
- The second arguments maps dispatch functions into `<MyComponent>` _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 `<MyComponent>` into `<ConnectedComponent>` - 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 :)

View File

@@ -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 = () => {
<Text>
{itemCount} item{itemCount === 1 ? '' : 's'} left
</Text>
<DefaultButton onClick={() => dispatch(actions.clear())}>Clear Completed</DefaultButton>
<DefaultButton onClick={() => clear()}>Clear Completed</DefaultButton>
</Stack>
);
};
const ConnectedTodoFooter = connect(
(state: Store) => ({
todos: state.todos
}),
dispatch => ({
clear: () => dispatch(actions.clear())
})
)(TodoFooter);
export { ConnectedTodoFooter as TodoFooter };

View File

@@ -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<TodoHeaderProps, TodoHeaderState> {
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 };

View File

@@ -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 = () => {
</Stack>
);
};
const ConnectedTodoList = connect((state: Store) => ({ ...state }))(TodoList);
export { ConnectedTodoList as TodoList };

View File

@@ -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<TodoListItemProps, TodoListItemState> {
class TodoListItem extends React.Component<TodoListItemProps, TodoListItemState> {
constructor(props: TodoListItemProps) {
super(props);
this.state = { editing: false, editLabel: undefined };
@@ -75,4 +81,13 @@ export class TodoListItem extends React.Component<TodoListItemProps, TodoListIte
};
}
TodoListItem.contextType = StoreContext;
const ConnectedTodoListItem = connect(
(state: Store) => ({ 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 };

View File

@@ -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(
<StoreContext.Provider value={store}>
<Provider store={store}>
<TodoApp />
</StoreContext.Provider>,
</Provider>,
document.getElementById('app')
);