mirror of
https://github.com/microsoft/frontend-bootcamp.git
synced 2026-01-26 14:56:42 +08:00
fixing up step 2-4 exercise
This commit is contained in:
@@ -2,108 +2,22 @@
|
||||
|
||||
[Lessons](../) | [Exercise](./exercise/) | [Demo](./demo/)
|
||||
|
||||
In this step, we describe some problems we encounter when creating a more complex application.
|
||||
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 4 to see results.
|
||||
|
||||
We will solve these problems with the React Context API. The Context API consists of:
|
||||
## TodoContext.Provider Component
|
||||
|
||||
1. Provider component
|
||||
2. Consuming context from a Class Component
|
||||
3. Consuming context from a Functional Component
|
||||
1. Open `exercise/src/components/TodoApp.tsx`
|
||||
|
||||
---
|
||||
2. Uncomment the missing functions inside the value prop
|
||||
|
||||
React represents a single component like this:
|
||||
## TodoFooter, Context inside Functional Component
|
||||
|
||||
```
|
||||
(props) => view;
|
||||
```
|
||||
1. Open `exercise/src/components/TodoFooter.tsx`
|
||||
|
||||
In a real application, these functions are composed. It looks more like this:
|
||||
2. Replace the two constants by using useContext(TodoContext)
|
||||
|
||||

|
||||
## TodoHeader, Context inside Class Component
|
||||
|
||||
## Problems in a Complex Application
|
||||
1. Open `exercise/src/components/TodoHeader.tsx`
|
||||
|
||||
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. This is a problem called **props drilling**
|
||||
|
||||
2. There is a lack of coordination of changes that can happen to the data
|
||||
|
||||
Even in our simple application, we saw this problem. For example, `<TodoList>` has this props interface:
|
||||
|
||||
```ts
|
||||
interface TodoListProps {
|
||||
complete: (id: string) => void;
|
||||
remove: (id: string) => void;
|
||||
todos: Store['todos'];
|
||||
filter: FilterTypes;
|
||||
edit: (id: string, label: string) => void;
|
||||
}
|
||||
```
|
||||
|
||||
All of these props are not used, except to be passed down to a child Component, `TodoListItem`:
|
||||
|
||||
```js
|
||||
<TodoListItem todos="{todos}" complete="{complete}" remove="{remove}" edit="{edit}" />
|
||||
```
|
||||
|
||||
## Context API
|
||||
|
||||
Let's solve these problems with the React Context API. _context_ is React's way to share data from components to their descendant children components without explicitly passing down through props at every level of the tree. React context is created by calling `createContext()` with some initial data. Use the `<TodoContext.Provider>` component to wrap a part of the component tree that should be handed the _context_.
|
||||
|
||||
```js
|
||||
// To create a completed empty context
|
||||
const TodoContext = React.createContext(undefined);
|
||||
|
||||
class TodoApp extends React.Component {
|
||||
render() {
|
||||
|
||||
// Pass in some state and function to the provider's value prop
|
||||
return (
|
||||
<TodoContext.Provider
|
||||
value={{
|
||||
...this.state,
|
||||
addTodo={this._addTodo},
|
||||
setFilter={this._setFilter},
|
||||
/* same goes for remove, complete, and clear */
|
||||
}}>
|
||||
<div>
|
||||
<TodoHeader />
|
||||
<TodoList />
|
||||
<TodoFooter />
|
||||
</div>
|
||||
</TodoContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Consume _context_ from a Class Component
|
||||
|
||||
Inside the children components, like the `<TodoHeader>` component, the value can be access from the component's `context` prop like this:
|
||||
|
||||
```js
|
||||
class TodoHeader extends React.Component {
|
||||
render() {
|
||||
// Step 1: use the context prop
|
||||
return <div>Filter is {this.context.filter}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: be sure to set the contextType property of the component class
|
||||
TodoHeader.contextType = TodoContext;
|
||||
```
|
||||
|
||||
### Consume _context_ from a Functional Component
|
||||
|
||||
If you're using the functional component syntax, you can access the context with the `useContext()` function. `useContext()` requires a recent release of React (16.8):
|
||||
|
||||
```js
|
||||
const TodoFooter = props => {
|
||||
const context = useContext(TodoContext);
|
||||
return (
|
||||
<div>
|
||||
<button onClick={context.clear()}>Clear Completed</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
2. Replace the couple of TODO
|
||||
|
||||
@@ -20,13 +20,14 @@ export class TodoApp extends React.Component<any, Store> {
|
||||
return (
|
||||
<TodoContext.Provider
|
||||
value={{
|
||||
...this.state,
|
||||
addTodo: this._addTodo,
|
||||
remove: this._remove,
|
||||
complete: this._complete,
|
||||
clear: this._clear,
|
||||
setFilter: this._setFilter,
|
||||
edit: this._edit
|
||||
...this.state
|
||||
// TODO: put the missing functions into the context value
|
||||
// addTodo: this._addTodo,
|
||||
// remove: this._remove,
|
||||
// complete: this._complete,
|
||||
// clear: this._clear,
|
||||
// setFilter: this._setFilter,
|
||||
// edit: this._edit
|
||||
}}
|
||||
>
|
||||
<Stack horizontalAlign="center">
|
||||
|
||||
@@ -3,15 +3,19 @@ import { DefaultButton, Stack, Text } from 'office-ui-fabric-react';
|
||||
import { TodoContext } from '../TodoContext';
|
||||
|
||||
export const TodoFooter = () => {
|
||||
const context = useContext(TodoContext);
|
||||
const itemCount = Object.keys(context.todos).filter(id => !context.todos[id].completed).length;
|
||||
// TODO: replace the following with a useContext(TodoContext) calls
|
||||
const todos = {};
|
||||
const clear = () => {};
|
||||
// - end of exercise for this file -
|
||||
|
||||
const itemCount = Object.keys(todos).filter(id => !todos[id].completed).length;
|
||||
|
||||
return (
|
||||
<Stack horizontal horizontalAlign="space-between">
|
||||
<Text>
|
||||
{itemCount} item{itemCount === 1 ? '' : 's'} left
|
||||
</Text>
|
||||
<DefaultButton onClick={() => context.clear()}>Clear Completed</DefaultButton>
|
||||
<DefaultButton onClick={() => clear()}>Clear Completed</DefaultButton>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -48,7 +48,8 @@ export class TodoHeader extends React.Component<{}, TodoHeaderState> {
|
||||
}
|
||||
|
||||
private onAdd = () => {
|
||||
this.context.addTodo(this.state.labelInput);
|
||||
// TODO: insert a this.context.addTodo call
|
||||
// HINT: this.context.addTodo(this.state.labelInput);
|
||||
this.setState({ labelInput: undefined });
|
||||
};
|
||||
|
||||
@@ -57,8 +58,9 @@ export class TodoHeader extends React.Component<{}, TodoHeaderState> {
|
||||
};
|
||||
|
||||
private onFilter = (item: PivotItem) => {
|
||||
this.context.setFilter(item.props.headerText as FilterTypes);
|
||||
// TODO: insert a this.context.setFilter call
|
||||
// HINT: this.context.setFilter(item.props.headerText as FilterTypes);
|
||||
};
|
||||
}
|
||||
|
||||
TodoHeader.contextType = TodoContext;
|
||||
// TODO: TodoHeader.contextType = TodoContext;
|
||||
|
||||
Reference in New Issue
Block a user