Rewrite of Day 1 to use modern React (#294)

* update to hooks

* more class to function

* cleanup

* finish ts final

* update html lesson

* add lessons page

* clean up

* move getters into context

* adding type

* fix bug

* step 5 cleanup

* init final pass

* text tweak

* fix ternaries

* readme cleanup

* fixed root readme
This commit is contained in:
Micah Godbolt
2022-01-13 09:22:50 -08:00
committed by GitHub
parent 4998c158d2
commit 7cea32428e
60 changed files with 923 additions and 929 deletions

View File

@@ -4,28 +4,22 @@ If you don't already have the app running, start it by running `npm start` from
## TodoFooter
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)`
1. Open TodoFooter and write a `TodoFooterProps` interface. It should include two values, a `clearCompleted` and `todos`. Use this interface in the function props like this: `(props: TodoFooterProps)`
2. Write an `_onClick` function that calls `props.clear`.
2. Write an `handleClick` 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`.
- Remember to use an arrow function to define this click handler.
3. Assign `_onClick` to the button's `onClick` prop. You won't need to use `this` since the component isn't a class.
3. Assign `handleClick` to the button's `onClick` prop.
4. Test out this functionality. Check a few todos complete and click the `Clear Completed` button.
## TodoHeader
1. Open TodoHeader and write `TodoHeaderProps` which will include `addTodo`, `setFilter` and `filter`. Replace the first `any` in the class declaration with this interface.
1. Open TodoHeader then write and use the `TodoHeaderProps` which will include `addTodo`, `changeFilter` and `filter`.
2. This component also has state. Write `TodoHeaderState` (there's just one value), and add this where the second `any` was.
2. Add `onFilter` to each of the filter buttons
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!
- Note that we can't add new parameters to onClick, but we can pull information from the event target!
- Remember to use an arrow function for this one too
4. Call `_onAdd` from the submit button
4. Call `onSubmit` from the submit button
5. Check out this new functionality! We can now add and filter todos!

View File

@@ -2,86 +2,75 @@ import React from 'react';
import { TodoFooter } from './components/TodoFooter';
import { TodoHeader } from './components/TodoHeader';
import { TodoList } from './components/TodoList';
import { Todos, FilterTypes } from './TodoApp.types';
import { Todo, Todos, FilterTypes } from './TodoApp.types';
let index = 0;
const defaultTodos: Todos = [
{
id: '04',
label: 'Todo 4',
status: 'completed',
},
{
id: '03',
label: 'Todo 3',
status: 'active',
},
{
id: '02',
label: 'Todo 2',
status: 'active',
},
{
id: '01',
label: 'Todo 1',
status: 'active',
},
];
export class TodoApp extends React.Component<any, { todos: Todos; filter: FilterTypes }> {
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'
export const TodoApp = () => {
const [filter, setFilter] = React.useState('all');
const [todos, setTodos] = React.useState<Todos>(defaultTodos);
const addTodo = (label: string): void => {
const getId = () => Date.now().toString();
const newTodo: Todo = {
id: getId(),
label: label,
status: 'active',
};
}
render() {
const { filter, todos } = this.state;
return (
<div>
<TodoHeader addTodo={this._addTodo} setFilter={this._setFilter} filter={filter} />
<TodoList complete={this._complete} todos={todos} filter={filter} />
<TodoFooter clear={this._clear} todos={todos} />
</div>
);
}
// business logic
private _addTodo = label => {
const { todos } = this.state;
const id = index++;
this.setState({
todos: { ...todos, [id]: { label, completed: false } }
});
setTodos([...todos, newTodo]);
};
private _complete = id => {
const { todos } = this.state;
const todo = todos[id];
const newTodos = { ...todos, [id]: { ...todo, completed: !todo.completed } };
this.setState({
todos: newTodos
});
};
private _clear = () => {
const { todos } = this.state;
const newTodos = {};
Object.keys(this.state.todos).forEach(id => {
if (!todos[id].completed) {
newTodos[id] = todos[id];
const toggleCompleted = (id: string) => {
const newTodos = todos.map((todo): Todo => {
if (todo.id === id) {
return { ...todo, status: todo.status === 'active' ? 'completed' : 'active' };
} else {
return todo;
}
});
this.setState({
todos: newTodos
});
setTodos(newTodos);
};
private _setFilter = filter => {
this.setState({
filter: filter
const clearCompleted = () => {
const updatedTodos = todos.map((todo): Todo => {
if (todo.status === 'completed') {
return { ...todo, status: 'cleared' };
} else {
return todo;
}
});
setTodos(updatedTodos);
};
const changeFilter = (filter: FilterTypes) => {
setFilter(filter);
};
return (
<div>
<TodoHeader filter={filter} changeFilter={changeFilter} addTodo={addTodo} />
<TodoList todos={todos} filter={'all'} toggleCompleted={toggleCompleted} />
<TodoFooter todos={todos} clearCompleted={clearCompleted} />
</div>
);
}

View File

@@ -1,12 +1,16 @@
export type FilterTypes = 'all' | 'active' | 'completed';
export type TodoType = 'active' | 'completed' | 'cleared';
export type CompleteTodo = (id) => null;
export interface TodoItem {
export interface Todo {
id: string;
label: string;
completed: boolean;
status: TodoType;
}
export interface Todos {
[id: string]: TodoItem;
}
export type Todos = Todo[];
export type AddTodo = (label: string) => void;
export type ToggleCompleted = (id: string) => void;
export type ClearCompleted = () => void;
export type ChangeFilter = (filter: FilterTypes) => void;

View File

@@ -1,14 +1,19 @@
import React from 'react';
import { Todos } from '../TodoApp.types';
export const TodoFooter = props => {
const itemCount = Object.keys(props.todos).filter(id => !props.todos[id].completed).length;
export const TodoFooter = (props) => {
const { clearCompleted, todos } = props;
const itemCount = todos.filter((todo) => todo.status === 'active').length;
return (
<footer>
<span>
{itemCount} item{itemCount === 1 ? '' : 's'} left
</span>
<button className="submit">Clear Completed</button>
<button className="submit">
Clear Completed
</button>
</footer>
);
};

View File

@@ -1,40 +1,34 @@
import React from 'react';
import { FilterTypes } from '../TodoApp.types';
export class TodoHeader extends React.Component<any, any> {
constructor(props) {
super(props);
this.state = { labelInput: '' };
}
export const TodoHeader = (props) => {
const [inputText, setInputText] = React.useState<string>('');
const { filter, addTodo, changeFilter } = props;
render() {
const { filter } = this.props;
return (
<header>
<h1>todos <small>(1.7 exercise)</small></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>
);
}
_onFilter = evt => {
this.props.setFilter(evt.target.innerText);
const onInput = (e) => {
setInputText(e.target.value);
};
_onChange = evt => {
this.setState({ labelInput: evt.target.value });
const onSubmit = () => {
if (inputText.length > 0) addTodo(inputText);
setInputText('');
};
_onAdd = () => {
this.props.addTodo(this.state.labelInput);
this.setState({ labelInput: '' });
const onFilter = (e) => {
changeFilter(e.currentTarget.textContent)
};
}
return (
<header>
<h1>todos <small>(1.6 exercise)</small></h1>
<div className="addTodo">
<input value={inputText} onChange={onInput} 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>
);
}

View File

@@ -3,26 +3,26 @@ import { TodoListItem } from './TodoListItem';
import { FilterTypes, Todos } from '../TodoApp.types';
interface TodoListProps {
complete: (id: string) => void;
todos: Todos;
filter: FilterTypes;
toggleCompleted: (id: string) => void;
todos: Todos;
}
export class TodoList extends React.Component<TodoListProps, any> {
render() {
const { filter, todos, complete } = this.props;
export const TodoList = (props: TodoListProps) => {
const { filter, todos, toggleCompleted } = 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);
});
const filteredTodos = todos.filter((todo) => {
if (todo.status === 'cleared') return false;
return filter === 'all' ||
(filter === 'completed' && todo.status === 'completed') ||
(filter === 'active' && todo.status === 'active');
});
return (
<ul className="todos">
{filteredTodos.map(id => (
<TodoListItem key={id} id={id} complete={complete} {...todos[id]} />
))}
</ul>
);
}
return (
<ul className="todos">
{filteredTodos.map((todo) => (
<TodoListItem key={todo.id} {...todo} toggleCompleted={toggleCompleted} />
))}
</ul>
);
}

View File

@@ -1,21 +1,20 @@
import React from 'react';
import { TodoItem } from '../TodoApp.types';
import { Todo, ToggleCompleted } from '../TodoApp.types';
interface TodoListItemProps extends TodoItem {
id: string;
complete: (id: string) => void;
interface TodoListItemProps extends Todo {
toggleCompleted: ToggleCompleted;
}
export class TodoListItem extends React.Component<TodoListItemProps, any> {
render() {
const { label, completed, complete, id } = this.props;
export const TodoListItem = (props: TodoListItemProps) => {
const { label, status, id, toggleCompleted } = props;
return (
<li className="todo">
<label>
<input type="checkbox" checked={completed} onChange={() => complete(id)} /> {label}
</label>
</li>
);
}
}
const handleCheck = () => toggleCompleted(id);
return (
<li className="todo">
<label>
<input type="checkbox" checked={status === 'completed'} onChange={handleCheck} /> {label}
</label>
</li>
);
};