In this post, we will discuss how to update state in correct way
first, App.js that contain list of todos and pass them as props to Todos component, after this we receive props in Todos component and passing them as props to Todo component to render every todo in list of todos, in Todos.js there is handleToggleComplete to make checkbox marked or not and contain handleSelect to detect which todo is selected
App.js code :
import Todos from "./Todos";
export default function App() {
const initialTodos = [
{
id : 1 ,
complete : false ,
name : 'first todo'
} ,
{
id : 2 ,
complete : true ,
name : 'second todo'
} ,
{
id : 3 ,
complete : false ,
name : 'third todo'
}
]
return (
<div className="App">
<Todos initialTodos = {initialTodos} />
</div>
);
}
Todos.js code :
import React, { useState } from "react";
import Todo from "./Todo";
const Todos = ({ initialTodos }) => {
const [todos, setTodos] = useState(initialTodos);
const [selectedTodo, setSelectedTodo] = useState();
const handleToggleComplete = (todoId) => {
setTodos((currTodos) => {
const todo = currTodos.find((todo) => todo.id === todoId);
todo.complete = !todo.complete;
return currTodos;
});
};
const handleSelect = (todoId) => {
setSelectedTodo(todos.find((todo) => todo.id === todoId));
};
return (
<>
{todos.map((todo) => (
<Todo
key={todo.id}
todo={todo}
handleToggleComplete={handleToggleComplete}
handleSelect={handleSelect}
/>
))}
<h3>Selected Todo</h3>
{selectedTodo && (
<Todo
todo={selectedTodo}
handleToggleComplete={handleToggleComplete}
handleSelect={handleSelect}
/>
)}
</>
);
};
export default Todos;
Todo.js code :
import React from "react";
const Todo = ({ todo, handleToggleComplete, handleSelect }) => {
const toggleComplete = () => {
handleToggleComplete(todo.id);
};
const onSelect = () => {
handleSelect(todo.id);
};
return (
<div>
<input
type="checkbox"
checked={todo.complete}
onChange={toggleComplete}
/>
{todo.name}
<button onClick={onSelect}>Select</button>
</div>
);
};
export default Todo;
now let's see the result of this code
as we see we can't update any checkbox, this because we update state directly, in setTodos we update currTodos directly and return currTodos
const handleToggleComplete = (todoId) => {
setTodos((currTodos) => {
const todo = currTodos.find((todo) => todo.id === todoId);
todo.complete = !todo.complete;
return currTodos;
});
};
to solve this problem we can update state by take copy of currTodos and update through copied state
const handleToggleComplete = (todoId) => {
setTodos((currTodos) => {
const copy = [...currTodos]
return copy.map(todo => {
if(todo.id === todoId) {
return {...todo , complete : !todo.complete}
}
return todo
})
});
};
to make more clarification about how to update state in code above,
1- copy is array we make update through it
2- if there is update return {...todo , complete : !todo.complete} this will be updated in copy array
3- if there is no update return todo
4- all this array (copy array) will be returned as new state as we say setTodos(copy)
return copy.map(todo => {
if(todo.id === todoId) {
return {...todo , complete : !todo.complete}
}
return todo
})
let's see the result now :
now we can update state of each checkbox successfully, but let's see what about the selectedTodo :
as we see, we selected third todo, but when mark or unmark checkbox from third todo it didn't update on selectedTodo that is becuase we stored the derived state of selectedTodo
in this code below the wrong is we store derived state, so keep in mind don't store derived state
setSelectedTodo(todos.find((todo) => todo.id === todoId))
the correct way is using this line of code below so every re-render we calculate updated selectedTodo and we can using useMemo for this
const selectedTodo = todos.find(todo => todo.id === selectedTodoId)
and that is the code below in Todos.js with all changes
import React, { useState } from "react";
import Todo from "./Todo";
const Todos = ({ initialTodos }) => {
const [todos, setTodos] = useState(initialTodos);
const [selectedTodoId, setSelectedTodoId] = useState();
const selectedTodo = todos.find(todo => todo.id === selectedTodoId)
const handleToggleComplete = (todoId) => {
setTodos((currTodos) => {
const copy = [...currTodos]
return copy.map(todo => {
if(todo.id === todoId) {
return {...todo , complete : !todo.complete}
}
return todo
})
});
};
return (
<>
{todos.map((todo) => (
<Todo
key={todo.id}
todo={todo}
handleToggleComplete={handleToggleComplete}
handleSelect={setSelectedTodoId}
/>
))}
<h3>Selected Todo</h3>
{selectedTodo && (
<Todo
todo={selectedTodo}
handleToggleComplete={handleToggleComplete}
handleSelect={setSelectedTodoId}
/>
)}
</>
);
};
export default Todos;
let's see the result now of this code :
That is all about how to update state in correct way, keep in mind always not update state directly, take copy of state and update through the copied state
Top comments (0)