I have components DoneTodos and Todo. First one uses the second one and should return rendered list of Todos which are ‘done’ ( In todos i have this key-value, it’s false by default ). Here they are:
Todo.jsx:
export default function Todo({todo}) {
function handleCheck() {
todo.done = true;
console.log(todo);
}
return (
<>
<input type="checkbox" onChange={handleCheck}/>
<span>
{todo.text}
</span>
</>
);
}
DoneTodos,jsx:
import Todo from "./Todo.jsx";
import {useState} from "react";
// TODO: Button doesn't work at all. Do smth about it
export default function DoneTodos({todos}){
const [state, setState] = useState(false);
let displayTodos = [];
function handleDoneTodos() {
for(let i = 0; i < todos.length; i++) {
if (todos[i].done === true) {
displayTodos.push(todos[i]);
}
}
setState(true);
}
if (state !== false) {
return (
<>
<ul>
{
displayTodos.map((todo, key) => (
<li key={key}>
<Todo todo={todo}></Todo>
</li>
))
}
</ul>
</>
);
}
return (
<button onClick={handleDoneTodos}>
Show done tasks
</button>
);
}
and App.jsx where DoneTodos is implemented:
import './App.css'
import CreateTodos from "./components/CreateTodos.jsx";
import TodoList from "./components/TaskList.jsx";
import {useState} from "react";
import DoneTodos from "./components/DoneTodos.jsx";
export default function App() {
const [todos, setTodos] = useState([]);
return (
<>
<CreateTodos todos={todos} setTodos={setTodos} />
<TodoList todos={todos} />
<DoneTodos todos={todos} />
</>
)
}
I am new to React so maybe I’m not getting something. Help me please.
I have tried to create function handleDoneTodos without using state in it but it seems to me to be impossible.
EDIT: Let me show my CreateTodos file for full understanding
CreateTodos.jsx:
export default function CreateTodos({todos, setTodos}) {
let onEnterClick = event => {
if (event.key === 'Enter') {
setTodos([
...todos,
{
text: event.target.value,
done: false
} // save todo to todos list
]);
event.target.value = ''; // reset input
}
}
return (
<p>
<input type="text" placeholder="Enter your task here!" onKeyDown={onEnterClick} />
</p>
);
}
3
Answers
Let me give you an idea. You have to handle how you did it for ‘Create todo’ option using
useState
function.But at the same time, you also need an unique identifier for each todo you create. With that identifier, we would easily modify the todo’s state.
Consider the below example,
Now where every you need to update the todo in your child component, just call
onComplete(2)
oronCreate("Buy coffee powder at evening")
.It’s entirely possible since you do actually update the
state
state which triggers a component rerender. The issue here is the order of operations. ThehandleDoneTodos
mutates the localdisplayTodos
variable then enqueues astate
state update. This triggers a component rerender anddisplayTodos
is redeclared an empty array on the next render cycle.The good news here is that what you want to render is completely derived "state", so you only need to map the done todos under the correct condition. Here you can simply filter the todos inline when mapping to the list items.
Example:
Other issues I see in the code are state/prop/object mutations. Not mutating references is one of the React Commandments. You should issue a state update to correctly update from the previous state and provide new object references for the new state values.
I suggest a bit of a refactor of your code to:
done
state.todos
state and how it can be updated.App
CreateTodos (Example since you didn’t include your code)
Todo
Demo
React Functional Component: Understanding State and Re-renders
Drew’s answer already contains the necessary corrections, but let’s dive deeper into why your initial code failed to display the list of done todos and gain a better understanding of how state works in React.
React State and Re-renders
In React, when you define state using the
useState
hook, React employs closures to ensure the persistence of state values between re-renders. This ensures that your state values do not get cleared during re-renders. A re-render is essentially React running the component function again to make necessary DOM updates.Two fundamental ways to trigger a re-render in React are:
setState
.When you call
setState
, here’s a simplified explanation of what happens:setState
multiple times within the same component, all the updates will be enqueued and actually applied during the next render cycle.Here’s an example:
In the above example, the
console.log
will log0
, not1
, because during the current render cycle, the state is still0
. The state becomes1
in the next render cycle after the user clicks the button again.Why the Original Code Didn’t Work
Let’s analyze your original code:
The issue in your original code is that
displayTodos
is an array created outside of the component’s state. This means it gets recreated as an empty array on every render. So, when you trigger a re-render by callingsetState(true)
,displayTodos
is reset to an empty array, causing the list to be empty.A Better Solution
As Drew mentioned, a more efficient solution is to use the
filter
method to conditionally render the done todos.