Untitled
unknown
tsx
4 years ago
3.1 kB
10
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...