|
1 |
| -import {useState} from 'react'; |
| 1 | +import {useEffect, useState} from 'react'; |
2 | 2 | import {v4} from 'uuid';
|
3 | 3 |
|
4 | 4 | function createTodo(title, done) {
|
5 | 5 | return {id: v4(), title, done};
|
6 | 6 | }
|
7 | 7 |
|
8 |
| -const TodoList = () => { |
9 |
| - const [todos, setTodoList] = useState(() => [ |
| 8 | +const fetchTodosFromApi = async () => { |
| 9 | + return [ |
10 | 10 | createTodo('Frozen yoghurt', false),
|
11 | 11 | createTodo('Ice cream sandwich', false),
|
12 | 12 | createTodo('Eclair', false),
|
13 | 13 | createTodo('Cupcake', false),
|
14 | 14 | createTodo('Gingerbread', false),
|
15 |
| - ]); |
| 15 | + ]; |
| 16 | +}; |
| 17 | + |
| 18 | +const TodoList = () => { |
| 19 | + const [todos, setTodoList] = useState([]); |
| 20 | + useEffect(function callApi() { |
| 21 | + //1. Imagine that we are calling an api to get todos |
| 22 | + fetchTodosFromApi().then(list => setTodoList(list)); |
| 23 | + }, []); |
| 24 | + |
| 25 | + //2. And we have a state for 2 counter |
| 26 | + const [ongoingCount, setOngoingCount] = useState(0); |
| 27 | + const [doneCount, setDoneCount] = useState(0); |
| 28 | + |
| 29 | + // 3. We will update the counters when the list changes |
| 30 | + useEffect(function refreshCounters() { |
| 31 | + setDoneCount(todos.filter(t => t.done).length); |
| 32 | + setOngoingCount(todos.filter(t => !t.done).length); |
| 33 | + }, [todos]); |
| 34 | + |
| 35 | + //4. See how many render ? |
| 36 | + console.log('render with', {total: todos.length, ongoingCount, doneCount}); |
| 37 | + // On first load it renders 3 times: |
| 38 | + // render with { total: 0, ongoingCount: 0, doneCount: 0 } ==> Before "callApi" |
| 39 | + // render with { total: 5, ongoingCount: 0, doneCount: 0 } ==> After "callApi" |
| 40 | + // render with { total: 5, ongoingCount: 5, doneCount: 0 } ==> After "refreshCounters". React is smart an only re-render when the value really changes. Here "doneCount" is "updated" from 0 to 0, so it does not re-render |
| 41 | + |
| 42 | + // When I set one of the todos as "done", it re-renders only 2 times (but 3 things changed: todos, ongoingCount and doneCount, do you know why?) |
| 43 | + // render with { total: 5, ongoingCount: 5, doneCount: 0 } ==> when setTodoList is called |
| 44 | + // render with { total: 5, ongoingCount: 4, doneCount: 1 } ==> when setDoneCount/setOngoingCount is called (after useEffect) |
| 45 | + |
| 46 | + //2 conclusions: |
| 47 | + // - We have too many render! |
| 48 | + // - React seems to wait the end of the useEffect to re-render the component (smart!) |
16 | 49 |
|
17 | 50 | const addEmptyTodo = () => setTodoList([createTodo('Relax! Edition will come...', false), ...todos]);
|
| 51 | + const markAsDone = (index) => setTodoList([...todos.slice(0, index), { |
| 52 | + ...todos[index], |
| 53 | + done: true, |
| 54 | + }, ...todos.slice(index + 1)]); |
| 55 | + |
18 | 56 |
|
19 | 57 | return <>
|
20 | 58 | <table>
|
21 | 59 | <thead>
|
22 | 60 | <tr>
|
23 |
| - <th rowSpan={2} align="left">My todos <button onClick={addEmptyTodo}>Add</button></th> |
| 61 | + <th rowSpan={2} align="left">My todos ({ongoingCount} ongoing /{doneCount} done) |
| 62 | + <button onClick={addEmptyTodo}>Add</button> |
| 63 | + </th> |
24 | 64 | </tr>
|
25 | 65 | </thead>
|
26 | 66 | <tbody>
|
27 |
| - {todos.map((todo) => ( |
| 67 | + {todos.map((todo, index) => ( |
28 | 68 | <tr key={todo.id}>
|
29 | 69 | <td>{todo.title}</td>
|
30 |
| - <td><input type="checkbox" value="1" checked={todo.done} disabled={true}/></td> |
| 70 | + <td><input type="checkbox" value="1" checked={todo.done} onChange={() => markAsDone(index)}/></td> |
31 | 71 | </tr>
|
32 | 72 | ))}
|
33 | 73 | </tbody>
|
|
0 commit comments