Untitled
unknown
tsx
4 years ago
3.1 kB
6
Indexable
import React, { useEffect, useMemo, useState } from 'react'; type Props = { todos?: TodoItem[]; }; type TodoItem = { id: string | number; title: string; isDone?: boolean; }; const App: React.FC<Props> = (props) => { return ( <div> <NotTouchingTheTree todos={props.todos} /> <ExpensiveTree /> </div> ); }; // We rerender this whole thing no matter what. If we want to improve performance, we can only separate the state from ExpensiveTree or memoize it const NotTouchingTheTree: React.FC<Props> = (props) => { const [todos, setTodos] = useState<TodoItem[]>(props.todos ?? []); const [filter, setFilter] = useState<'all' | 'undone'>('all'); useEffect(() => { if (props.todos?.length) { setFilter('all'); // did we want to reset filter on props change here? } }, [props.todos]); const addTodo = () => { const title = prompt('What to do?'); if (title) { setTodos((todos) => { return [...todos, { id: Date.now(), title }]; }); } }; const markAsDone = (todo: TodoItem) => { const index = todos.findIndex((item) => item.id === todo.id); if (index >= 0) { const res = updateAt(todos, index, { ...todo, isDone: true }); setTodos(res); } }; const markAsUndone = (todo: TodoItem) => { const index = todos.findIndex((item) => item.id === todo.id); if (index >= 0) { const res = updateAt(todos, index, { ...todo, isDone: false }); setTodos(res); } }; const deleteTodo = (todo: TodoItem) => { setTodos((todos) => todos.filter((item) => item.id !== todo.id)); }; const onFilterButtonClick = () => { setFilter((filter) => (filter === 'all' ? 'undone' : 'all')); }; const filteredTodos = useMemo(() => { return todos.filter((todo) => (filter === 'undone' ? !todo.isDone : true)); }, [todos, filter]); const doneCount = useMemo(() => { return todos.reduce((count, todo) => count + (todo.isDone ? 1 : 0), 0); }, [todos]); return ( <> <p>{`${doneCount} / ${todos.length}`}</p> <ul> {filteredTodos.map((todo) => ( <div key={`${todo.id}`}> <p>{`${todo.isDone ? '✅ ' : ''}${todo.title}`}</p> <button onClick={() => { todo.isDone ? markAsUndone(todo) : markAsDone(todo); }} > Done </button> <button onClick={() => deleteTodo(todo)}>Delete</button> </div> ))} </ul> {/* we can useCallback on these but there is no reason for it with plain DOM elements */} <button onClick={addTodo}>Add</button> <button onClick={onFilterButtonClick}> {`Show ${filter === 'all' ? 'undone' : 'all'} todos`} </button> </> ); }; function updateAt<T>(arr: T[], index: number, value: T): T[] { return [...arr.slice(0, index), value, ...arr.slice(index + 1)]; } function ExpensiveTree() { let now = performance.now(); while (performance.now() - now < 1000) { // Artificial delay -- do nothing for 1000ms } return null; } export default App;
Editor is loading...