mirror of
https://github.com/microsoft/frontend-bootcamp.git
synced 2026-01-26 14:56:42 +08:00
readme cleanup
This commit is contained in:
@@ -53,8 +53,7 @@
|
||||
<div class="Tile-link">
|
||||
React Components
|
||||
<div class="Tile-links">
|
||||
<a target="_blank" href="./step1-05/demo/">demo</a> | <a target="_blank" href="./step1-05/exercise/">exercise</a> |
|
||||
<a target="_blank" href="./step1-05/final/">final</a>
|
||||
<a target="_blank" href="./step1-05/demo/">demo</a> | <a target="_blank" href="./step1-05/exercise/">exercise</a>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
@@ -62,8 +61,7 @@
|
||||
<div class="Tile-link">
|
||||
State-Driven UI
|
||||
<div class="Tile-links">
|
||||
<a target="_blank" href="./step1-06/demo/">demo</a> | <a target="_blank" href="./step1-06/exercise/">exercise</a> |
|
||||
<a target="_blank" href="./step1-06/final/">final</a>
|
||||
<a target="_blank" href="./step1-06/demo/">demo</a> | <a target="_blank" href="./step1-06/exercise/">exercise</a>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Building a Static Page
|
||||
# Step 1-05: Demo Building a Static Page
|
||||
|
||||
To start off our todo application we are going to follow the steps outlined in [Thinking in React](https://reactjs.org/docs/thinking-in-react.html). The first step of the process is to break our application into a component hierarchy. For this app, we're going to keep it simple and just use four parts.
|
||||
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
## Exercise
|
||||
# Step 1-06 Exercise
|
||||
|
||||
### TodoFooter
|
||||
|
||||
1. Add a TodoFooter component, copying over the `<footer>` tag and all of its children from `TodoApp.html` in the `step1-05` folder.
|
||||
1. Add a TodoFooter component in the `components` folder, copying over the `<footer>` tag and all of its children from `TodoApp.html` in the `step1-05` folder. This could be a function or class.
|
||||
2. Remove any `onclick` properties, and change `class` to `className`
|
||||
|
||||
### TodoList
|
||||
|
||||
1. Add a TodoList component like you did with the footer.
|
||||
1. Add a TodoList component like you did with the footer. This could also be function or class.
|
||||
2. Import TodoListItem and add 4 of them inside of the `<ul>`
|
||||
3. Bonus points for using a [`for`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Loops_and_iteration) loop or using [`map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) to create 4 list items based on the array `[1,2,3,4]`
|
||||
|
||||
## App.tsx
|
||||
|
||||
1. Import both of these components into `App.tsx` and place their tags below the `TodoHeader`.
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import React from 'react';
|
||||
import { TodoFooter } from './components/TodoFooter';
|
||||
import { TodoHeader } from './components/TodoHeader';
|
||||
import { TodoList } from './components/TodoList';
|
||||
|
||||
export class TodoApp extends React.Component<any, any> {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<TodoHeader />
|
||||
<TodoList />
|
||||
<TodoFooter />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
export const TodoFooter = (props: any) => {
|
||||
return (
|
||||
<footer>
|
||||
<p>footer</p>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
export class TodoList extends React.Component<any, any> {
|
||||
render() {
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<link rel="stylesheet" href="./src/style.css" />
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import React from 'react';
|
||||
import { TodoFooter } from './components/TodoFooter';
|
||||
import { TodoHeader } from './components/TodoHeader';
|
||||
import { TodoList } from './components/TodoList';
|
||||
|
||||
export class TodoApp extends React.Component<any, any> {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<TodoHeader />
|
||||
<TodoList />
|
||||
<TodoFooter />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
export const TodoFooter = (props: any) => {
|
||||
return (
|
||||
<footer>
|
||||
<span>
|
||||
<span className="remaining">4</span> items left
|
||||
</span>
|
||||
<button className="submit">Clear Completed</button>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
@@ -1,20 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
export class TodoHeader extends React.Component<any, any> {
|
||||
render() {
|
||||
return (
|
||||
<header>
|
||||
<h1>todos - step1-05 final</h1>
|
||||
<div className="addTodo">
|
||||
<input className="textfield" placeholder="add todo" />
|
||||
<button className="submit">Add</button>
|
||||
</div>
|
||||
<nav className="filter">
|
||||
<button className="completed">all</button>
|
||||
<button>active</button>
|
||||
<button>completed</button>
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import React from 'react';
|
||||
import { TodoListItem } from './TodoListItem';
|
||||
|
||||
export class TodoList extends React.Component<any, any> {
|
||||
render() {
|
||||
return (
|
||||
<ul className="todos">
|
||||
<TodoListItem />
|
||||
<TodoListItem />
|
||||
<TodoListItem />
|
||||
<TodoListItem />
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
export class TodoListItem extends React.Component<any, any> {
|
||||
render() {
|
||||
return (
|
||||
<li className="todo">
|
||||
<label>
|
||||
<input type="checkbox" /> Todo 1
|
||||
</label>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { TodoApp } from './TodoApp';
|
||||
ReactDOM.render(<TodoApp />, document.getElementById('app'));
|
||||
@@ -1,49 +0,0 @@
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
width: 400px;
|
||||
margin: 20px auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.addTodo {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.textfield {
|
||||
flex-grow: 1;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.submit {
|
||||
border: none;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.filter {
|
||||
margin: 10px 0 0;
|
||||
}
|
||||
|
||||
.filter button {
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.filter .selected {
|
||||
border-bottom: 2px solid blue;
|
||||
}
|
||||
|
||||
.todos {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
footer {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
footer span {
|
||||
flex-grow: 1;
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
# Creating a State-Driven UI
|
||||
# Step 1-06 Demo: Creating a State-Driven UI
|
||||
|
||||
In React, the data travels in one direction: top-down in the form of state propagating down the component hierarchy. Only the component containing the state can change the state itself. When a UI interaction occurs, a stateful component must pass down an event handler to the UI component triggering the event in order to signal a state change.
|
||||
|
||||
## Demo
|
||||
|
||||
[Step #3 of "Thinking in React"](https://reactjs.org/docs/thinking-in-react.html) suggests finding the "minimal set of mutable state" that your application requires. So in this demo we are going to add that "minimal state" to our application and drive our UI off of that data. With that done, the next step will be to create ways to modify that state, which will in turn cascade down through our UI. This [reconciliation](https://reactjs.org/docs/reconciliation.html) process, figuring out what in your UI needs to change based on changing state, is what React excels at.
|
||||
|
||||
### Adding State to App
|
||||
## Adding State to `AppTodo.tsx`
|
||||
|
||||
Inside our `TodoApp` class, we will add the minimal state for our application, which includes just two keys: `todos` and `filter`. We don't need to worry about a `remaining` value because it can be calculated by counting the number of todos where the `completed` field is set to `false`.
|
||||
|
||||
@@ -34,56 +32,51 @@ constructor(props) {
|
||||
completed: false
|
||||
}
|
||||
},
|
||||
filter: 'all'
|
||||
filter: 'active'
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
> You could also use an array to represent your todos. Array manipulation can be easier in some cases, but this object approach simplifies other functionality and will ultimately be more performant.
|
||||
|
||||
### Passing State Through to UI
|
||||
|
||||
To avoid reaching into state over and over, we once again use destructuring to pull out the pieces we need.
|
||||
|
||||
```jsx
|
||||
const { filter, todos } = this.state;
|
||||
```
|
||||
|
||||
> Note that I've set `todos` to default to an empty array so that the `todos` variable is never undefined
|
||||
## Passing State Through to UI
|
||||
|
||||
Now we can pass `filter` and `todos` into our components.
|
||||
|
||||
```jsx
|
||||
return (
|
||||
<div>
|
||||
<TodoHeader filter={filter} />
|
||||
<TodoList todos={todos} filter={filter} />
|
||||
<TodoFooter todos={todos} />
|
||||
</div>
|
||||
);
|
||||
```
|
||||
|
||||
### State-Driven TodoList
|
||||
|
||||
I've already pulled out our props into `filter` and `todos` variables, and written a bit of JS that will return an array of filtered todo `id`s. We'll be using that filtered array to render our todo items.
|
||||
|
||||
```jsx
|
||||
{
|
||||
filteredTodos.map(id => <TodoListItem key={id} id={id} {...todos[id]} />);
|
||||
render() {
|
||||
const { filter, todos } = this.state;
|
||||
return (
|
||||
<div>
|
||||
<TodoHeader filter={filter} />
|
||||
<TodoList todos={todos} filter={filter} />
|
||||
<TodoFooter todos={todos} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
- [`map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map): This method iterates over the array it's called on, transforming each value with the passed in function and returning the values in a new array (our rendered TodoListItems).
|
||||
- `key`: We use the `id` from the `filterTodos` array as the [list item key](https://reactjs.org/docs/lists-and-keys.html) The keys should be unique as they help React track which items are added, removed, or updated and determine whether an instance of an item should be rerendered or a new one created.
|
||||
- `id`: The `key` is not actually passed into the component, so we pass the same value as `id` as well. This will help us out later.
|
||||
- `todos[id]`: Lastly we use the `id` to grab the todo from our `todos` object, then use the [spread operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) to pass through the todo's `label` and `completed` values.
|
||||
> This spread operator is the same as saying `label={todos[id].label} completed={todos[id].completed}`. Pretty obvious why spread is so handy!
|
||||
## State-Driven TodoList
|
||||
|
||||
### State-Driven and Stateful Header
|
||||
I've already pulled out our props into `filter` and `todos` variables, and written a bit of JS that will return an array of filtered todo `id`s. We'll be using that filtered array to render our todo items.
|
||||
|
||||
> `todos[id]` returns the todo matching the `id` passed in, and the spread operator (...) is the same as saying `label={todos[id].label} completed={todos[id].completed}`
|
||||
|
||||
```jsx
|
||||
return (
|
||||
<ul className="todos">
|
||||
{filteredTodos.map(id => (
|
||||
<TodoListItem key={id} id={id} {...todos[id]} />
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
```
|
||||
|
||||
## State-Driven and Stateful Header
|
||||
|
||||
Within the header we've got a situation where we not only want to pass `filter` state down to it, but we also want to maintain state within the control. Fortunately, this is no problem at all for React. First off let's deal with the incoming state.
|
||||
|
||||
#### Conditional Class Names
|
||||
### Conditional Class Names
|
||||
|
||||
In CSS-based styling, visual states are applied by adding and removing classes. We can use the filter value to conditionally add a class, thereby lighting up the correct filter button.
|
||||
|
||||
@@ -97,16 +90,14 @@ In CSS-based styling, visual states are applied by adding and removing classes.
|
||||
|
||||
> The [ternary operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator) `condition ? expressionIfTrue : expressionIfFalse` is widely used in React code, as each expression could be a string for a className or even a JSX element.
|
||||
|
||||
#### Adding a Controlled Input
|
||||
### Adding a Controlled Input
|
||||
|
||||
In React, form elements such as `<input>`, `<textarea>`, and `<select>` can be used as either **uncontrolled** or **controlled**. (This paradigm also applies to UI Fabric's customized implementations of form components, which we'll use later.)
|
||||
In React, form elements such as `<input>`, `<textarea>`, and `<select>` can be used as either **uncontrolled** or **controlled**.
|
||||
|
||||
An **uncontrolled input** maintains its current value internally and updates it based on user interactions (entering text, choosing options, etc). The code only pulls the value from the input when it's needed, such as on submit. This is similar to how inputs in a plain HTML form work.
|
||||
|
||||
A **controlled input** takes its current value from a prop and use a callback to notify the parent component of changes made by the user. The input's value doesn't change until the parent component updates the input's props in response to the callback.
|
||||
|
||||
Typically, a controlled input's current value is stored in the parent component's state (then passed to the input as a prop during render). The parent updates its state in response to the callback, which causes the input to be re-rendered with a new prop value. This round trip process might sound inefficient, but in reality it has little to no impact and helps enable some advanced form functionality.
|
||||
|
||||
> The distinction between controlled and uncontrolled is important to understand when writing or using form components, and misunderstandings of this concept are a very common source of bugs. See [this article](https://goshakkk.name/controlled-vs-uncontrolled-inputs-react/) for a more detailed explanation.
|
||||
|
||||
Let's try changing the text field in our `TodoHeader` component to a controlled input. To add a controlled input, we need two things, which our demo already provides:
|
||||
|
||||
@@ -3,17 +3,16 @@ import { TodoListItem } from './TodoListItem';
|
||||
|
||||
export class TodoList extends React.Component<any, any> {
|
||||
render() {
|
||||
const { filter, todos = [] } = this.props;
|
||||
const { filter, todos } = this.props;
|
||||
|
||||
// filteredTodos returns an array of filtered todo keys [01,02,03]
|
||||
const filteredTodos = Object.keys(todos).filter(id => {
|
||||
return filter === 'all' || (filter === 'completed' && todos[id].completed) || (filter === 'active' && !todos[id].completed);
|
||||
});
|
||||
|
||||
return (
|
||||
<ul className="todos">
|
||||
<TodoListItem />
|
||||
<TodoListItem />
|
||||
<TodoListItem />
|
||||
<TodoListItem />
|
||||
[01, 02, 03, 04].map((id)=> <TodoListItem />)
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@ import { TodoListItem } from './TodoListItem';
|
||||
|
||||
export class TodoList extends React.Component<any, any> {
|
||||
render() {
|
||||
const { filter, todos = [] } = this.props;
|
||||
const { filter, todos } = this.props;
|
||||
|
||||
// filteredTodos returns an array of filtered todo keys [01,02,03]
|
||||
const filteredTodos = Object.keys(todos).filter(id => {
|
||||
return filter === 'all' || (filter === 'completed' && todos[id].completed) || (filter === 'active' && !todos[id].completed);
|
||||
});
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<link rel="stylesheet" href="./src/style.css" />
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import React from 'react';
|
||||
import { TodoFooter } from './components/TodoFooter';
|
||||
import { TodoHeader } from './components/TodoHeader';
|
||||
import { TodoList } from './components/TodoList';
|
||||
|
||||
export class TodoApp extends React.Component<any, any> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
todos: {
|
||||
'04': {
|
||||
label: 'Todo 4',
|
||||
completed: true
|
||||
},
|
||||
'03': {
|
||||
label: 'Todo 3',
|
||||
completed: false
|
||||
},
|
||||
'02': {
|
||||
label: 'Todo 2',
|
||||
completed: false
|
||||
},
|
||||
'01': {
|
||||
label: 'Todo 1',
|
||||
completed: false
|
||||
}
|
||||
},
|
||||
filter: 'all'
|
||||
};
|
||||
}
|
||||
render() {
|
||||
const { filter, todos = [] } = this.state;
|
||||
return (
|
||||
<div>
|
||||
<TodoHeader filter={filter} />
|
||||
<TodoList todos={todos} filter={filter} />
|
||||
<TodoFooter todos={todos} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
export const TodoFooter = (props: any) => {
|
||||
const itemCount = Object.keys(props.todos).filter(id => !props.todos[id].completed).length;
|
||||
return (
|
||||
<footer>
|
||||
<span>
|
||||
{itemCount} item{itemCount === 1 ? '' : 's'} left
|
||||
</span>
|
||||
<button className="submit">Clear Completed</button>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
@@ -1,35 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
export class TodoHeader extends React.Component<any, any> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { labelInput: '' };
|
||||
}
|
||||
|
||||
render() {
|
||||
const { filter } = this.props;
|
||||
|
||||
return (
|
||||
<header>
|
||||
<h1>todos - step1-06 final</h1>
|
||||
<div className="addTodo">
|
||||
<input value={this.state.labelInput} onChange={this._onChange} className="textfield" placeholder="add todo" />
|
||||
<button className="submit">Add</button>
|
||||
</div>
|
||||
<nav className="filter">
|
||||
<button className={filter === 'all' ? 'selected' : ''}>all</button>
|
||||
<button className={filter === 'active' ? 'selected' : ''}>active</button>
|
||||
<button className={filter === 'completed' ? 'selected' : ''}>completed</button>
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
_onChange = evt => {
|
||||
this.setState({ labelInput: evt.target.value });
|
||||
};
|
||||
|
||||
_onAdd = () => {
|
||||
console.log(this.state.labelInput);
|
||||
this.setState({ labelInput: '' });
|
||||
};
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import React from 'react';
|
||||
import { TodoListItem } from './TodoListItem';
|
||||
|
||||
export class TodoList extends React.Component<any, any> {
|
||||
render() {
|
||||
const { filter, todos = [] } = this.props;
|
||||
|
||||
const filteredTodos = Object.keys(todos).filter(id => {
|
||||
return filter === 'all' || (filter === 'completed' && todos[id].completed) || (filter === 'active' && !todos[id].completed);
|
||||
});
|
||||
return (
|
||||
<ul className="todos">
|
||||
{filteredTodos.map(id => (
|
||||
<TodoListItem key={id} id={id} {...todos[id]} />
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
export class TodoListItem extends React.Component<any, any> {
|
||||
render() {
|
||||
const { label, completed } = this.props;
|
||||
// The "no-op" onChange handler prevents a console warning from React at runtime
|
||||
return (
|
||||
<li className="todo">
|
||||
<label>
|
||||
<input type="checkbox" checked={completed} onChange={() => undefined} /> {label}
|
||||
</label>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { TodoApp } from './TodoApp';
|
||||
ReactDOM.render(<TodoApp />, document.getElementById('app'));
|
||||
@@ -1,49 +0,0 @@
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
width: 400px;
|
||||
margin: 20px auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.addTodo {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.textfield {
|
||||
flex-grow: 1;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.submit {
|
||||
border: none;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.filter {
|
||||
margin: 10px 0 0;
|
||||
}
|
||||
|
||||
.filter button {
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.filter .selected {
|
||||
border-bottom: 2px solid blue;
|
||||
}
|
||||
|
||||
.todos {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
footer {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
footer span {
|
||||
flex-grow: 1;
|
||||
}
|
||||
@@ -120,35 +120,25 @@ Now that our interface is complete, try changing the word "all" in `filter === a
|
||||
Most of our components will need to specify types for `todos` and `filter`, so it's a good thing that TypeScript allows us to share types between files. I've already written up and exported those shared types in the file `TodoApp.types.ts`, so we just need to import them and use them in our interface.
|
||||
|
||||
```ts
|
||||
import { FilterTypes, Todos } from '../TodoApp.types';
|
||||
import { FilterTypes, Todos, CompleteTodo } from '../TodoApp.types';
|
||||
|
||||
interface TodoListProps {
|
||||
complete: (id: string) => void;
|
||||
complete: CompleteTodo;
|
||||
todos: Todos;
|
||||
filter: FilterTypes;
|
||||
}
|
||||
```
|
||||
|
||||
## Updating TodoApp
|
||||
|
||||
Our `TodoApp` doesn't take any props, but it does have state. We can use TypeScript to define that as well.
|
||||
|
||||
I've already imported `Todos` and `FilterTypes` into the `TodoApp`, so we just need to add them to our class. If we want, we can even skip a separate interface definition and just declare the type inline. (This is not recommended for types of any complexity or types that are used in multiple places.)
|
||||
|
||||
```ts
|
||||
export class TodoApp extends React.Component<{}, { todos: Todos; filter: FilterTypes }>
|
||||
```
|
||||
|
||||
> Note that the first value in `<>` always refers to props. Since `TodoApp` takes none, we'll set it to an empty object type.
|
||||
|
||||
## Writing TodoListItemProps
|
||||
|
||||
Jumping down to the TodoListItem, as we start to write the `TodoListItemProps` we realize that two of the props, `label` and `completed`, have already been defined in the `TodoItem` interface in `TodoApp.types`. So we can make `TodoListItemProps` reuse the `TodoItem` interface by extending it.
|
||||
Jumping down to the TodoListItem, as we start to write the `TodoListItemProps` we realize that two of the props, `label` and `completed`, have already been defined in the `TodoItem` interface. So we can make `TodoListItemProps` reuse the `TodoItem` interface by extending it.
|
||||
|
||||
```ts
|
||||
import { CompleteTodo } from '../TodoApp.types';
|
||||
|
||||
interface TodoListItemProps extends TodoItem {
|
||||
id: string;
|
||||
complete: (id: string) => void;
|
||||
complete: CompleteTodo;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -6,7 +6,12 @@ import { Todos, FilterTypes } from './TodoApp.types';
|
||||
|
||||
let index = 0;
|
||||
|
||||
export class TodoApp extends React.Component<any, any> {
|
||||
interface TodoAppState {
|
||||
todos: Todos;
|
||||
filter: FilterTypes;
|
||||
}
|
||||
|
||||
export class TodoApp extends React.Component<any, TodoAppState> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export type FilterTypes = 'all' | 'active' | 'completed';
|
||||
|
||||
export type CompleteTodo = (id) => null;
|
||||
|
||||
export interface TodoItem {
|
||||
label: string;
|
||||
completed: boolean;
|
||||
|
||||
@@ -6,6 +6,7 @@ export class TodoList extends React.Component<any, any> {
|
||||
render() {
|
||||
const { filter, todos, complete } = this.props;
|
||||
|
||||
// filteredTodos returns an array of filtered todo keys [01,02,03]
|
||||
const filteredTodos = Object.keys(todos).filter(id => {
|
||||
return filter === 'all' || (filter === 'completed' && todos[id].completed) || (filter === 'active' && !todos[id].completed);
|
||||
});
|
||||
|
||||
@@ -1,29 +1,27 @@
|
||||
## Exercise
|
||||
#Step 1-07: 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 1 step 7 to see results.
|
||||
## TodoFooter
|
||||
|
||||
### TodoFooter
|
||||
|
||||
1. Open TodoFooter and write a TodoFooterProps interface. It should include two values, a function and an object. Use this interface in the function props like this: `(props: TodoFooterProps)`
|
||||
1. Open TodoFooter and write a TodoFooterProps interface. It should include two values, a `clear` and `todos`. Use this interface in the function props like this: `(props: TodoFooterProps)`
|
||||
|
||||
2. Write an `_onClick` function that calls `props.clear`.
|
||||
|
||||
> Since TodoFooter is not a class, the `_onClick` function needs to be stored in a const placed before the `return`.
|
||||
|
||||
3. Assign `_onClick` to the button's `onClick` prop. You won't need to use `this` since the component isn't a class.
|
||||
|
||||
4. Test out this functionality. Check a few todos complete and click the `Clear Completed` button.
|
||||
|
||||
### TodoHeader
|
||||
## TodoHeader
|
||||
|
||||
1. Open TodoHeader and write TodoHeaderProps which will include three values. Replace the first `any` in the class declaration with this interface.
|
||||
1. Open TodoHeader and write TodoHeaderProps which will include `addTodo`, `setFilter` and `filter`. Replace the first `any` in the class declaration with this interface.
|
||||
|
||||
2. This component also has state. Write TodoHeaderState (there's just one value), and add this where the second `any` was.
|
||||
|
||||
3. Add `_onFilter` to each of the filter buttons
|
||||
|
||||
> Note that we can't add new parameters to onClick, but we can pull information from the event target!
|
||||
|
||||
4. Write an `_onAdd` method that calls `addTodo` on the current `labelInput`, then sets the `labelInput` in state to an empty string
|
||||
4. Call `_onAdd` from the submit button
|
||||
|
||||
5. Call `_onAdd` from the submit button
|
||||
|
||||
6. Check out this new functionality! We can now add and filter todos!
|
||||
5. Check out this new functionality! We can now add and filter todos!
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export type FilterTypes = 'all' | 'active' | 'completed';
|
||||
|
||||
export type CompleteTodo = (id) => null;
|
||||
|
||||
export interface TodoItem {
|
||||
label: string;
|
||||
completed: boolean;
|
||||
|
||||
@@ -32,4 +32,9 @@ export class TodoHeader extends React.Component<any, any> {
|
||||
_onChange = evt => {
|
||||
this.setState({ labelInput: evt.target.value });
|
||||
};
|
||||
|
||||
_onAdd = () => {
|
||||
this.props.addTodo(this.state.labelInput);
|
||||
this.setState({ labelInput: '' });
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ export class TodoList extends React.Component<TodoListProps, any> {
|
||||
render() {
|
||||
const { filter, todos, complete } = this.props;
|
||||
|
||||
// filteredTodos returns an array of filtered todo keys [01,02,03]
|
||||
const filteredTodos = Object.keys(todos).filter(id => {
|
||||
return filter === 'all' || (filter === 'completed' && todos[id].completed) || (filter === 'active' && !todos[id].completed);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user