Reputation: 289
I am trying to create my first Todo list with React.js. I am trying to change the state from
const [todos, setTodos] = useState([])
To:
const [todos, setTodos] = useState({
todo: [],
isCompleted: false,
})
Just to try and add in a isCompleted state. However, when I change it, I get an error when running my application from a previously working map. The error is in the title.
Could somebody tell me what is wrong?
Code:
TodosApp.js
import React, { useState } from "react"
import Todos from "./Todos"
const TodoApp = () => {
const [todos, setTodos] = useState({
todo: [],
isCompleted: false,
})
const [input, setInput] = useState("")
const handleCurrentInput = (e) => {
setInput(e.target.value)
}
const handleSubmit = (e) => {
e.preventDefault()
console.log(input)
setInput("")
setTodos({
...todos,
task: input,
isCompleted: false,
})
}
const handleDelete = ({ index }) => {
const newTodos = [...todos]
newTodos.splice(index, 1)
setTodos(newTodos)
}
return (
<div id="todoForm">
<div class="container">
<div class="todo_form">
<div class="todo_input">
<form onSubmit={handleSubmit}>
<input
type="text"
id="input_todo"
onChange={handleCurrentInput}
value={input}
/>
</form>
<Todos todos={todos} handleDelete={handleDelete} />
</div>
</div>
</div>
</div>
)
}
export default TodoApp
Todos.js
import React, { useState } from "react"
const Todos = (props) => {
return (
<ul>
{props.todos.map((todo, index) => {
return (
<li key={todo}>
{todo}
<button onClick={() => props.handleDelete({ index })}>
Delete
</button>
</li>
)
})}
</ul>
)
}
export default Todos
Upvotes: 0
Views: 444
Reputation: 8118
isCompleted
should be associated with each todo item.
So, you should use todos
as array and store objects within that array. Each object will have isCompleted
and a task
property along with a unique Id
as well.
const [todos, setTodos] = useState([]);
And your submit input would look like:
const handleSubmit = (e) => {
e.preventDefault();
const todo = {
task: input,
id: new Date().getTime().toString(),
isCompleted: false
};
const updatedTodos = [...todos, todo];
setTodos(updatedTodos);
console.log(updatedTodos);
setInput("");
};
Note: To generate unique Ids you can use uuid
library. I have generated unique ids here using id: new Date().getTime().toString()
.
FULL WORKING CODE SANDBOX LINK: https://codesandbox.io/s/todosissue-2mc26?file=/src/TodoApp.js
Have modified handleDelete
function as well :)
Upvotes: 0
Reputation: 10675
Working App: Stackblitz
import React, { useState, useEffect } from "react";
const TodoApp = () => {
/* initialize todos with array
instead of an object 👇*/
const [todos, setTodos] = useState([]);
const [input, setInput] = useState("");
const handleCurrentInput = e => {
setInput(e.target.value);
};
const handleSubmit = e => {
e.preventDefault();
console.log(input);
/* update the state by appending an object having
key todo and isCompleted to copy of our main state,
todos.👇
*/
setTodos([...todos, { todo: input, isCompleted: false }]);
setInput("");
};
const handleDelete = ({ index }) => {
const newTodos = [...todos];
newTodos.splice(index, 1);
setTodos(newTodos);
};
useEffect(() => {
console.log(JSON.stringify(todos));
}, [todos]);
return (
<div id="todoForm">
<div class="container">
<div class="todo_form">
<div class="todo_input">
<form onSubmit={handleSubmit}>
<input
type="text"
id="input_todo"
onChange={handleCurrentInput}
value={input}
/>
</form>
<Todos todos={todos} handleDelete={handleDelete} />
</div>
</div>
</div>
</div>
);
};
export default TodoApp;
const Todos = props => {
return (
<>
<ul>
{props.todos.map((todo, index) => {
return (
<li key={index}>
{/**use null propogation to avoid accessing the null todo value which will not exist in first render. */}
{todo?.todo}
<button onClick={() => props.handleDelete({ index })}>
Delete
</button>
</li>
);
})}
</ul>
</>
);
};
Upvotes: 0
Reputation: 1826
It's because you don't set to state the right way, todos
got overwritten with the wrong value. You should write:
// handleSubmit
setTodos(s => {
...s,
task: input,
isCompleted: false,
});
and
// handleDelete
const newTodos = [...todos]
newTodos.splice(index, 1)
setTodos(s => ({ ...s, todos: newTodos }))
Upvotes: 0
Reputation: 14228
You need to focus on each todo
item including 2 props task, isCompleted
instead of isCompleted
of todos
.
const [todos, setTodos] = useState([]);
var newTodo = {
task: 'React JS',
isCompleted: false
};
setTodos([...todos, newTodo]);
Then your todos
's structure like below:
[
{
task: 'Study React JS',
isCompleted: false
},
{
task: 'Study React Redux',
isCompleted: false
},
];
Upvotes: 2
Reputation: 1267
Your state is an object containing an array of todos. This is what you're passing to your Todos
component.
So you have two options:
todos.todos
as a prop orisCompleted
seems that it should be part of each todo, because each todo should be completed not the list itself. A list is completed if every todo isCompleted
So your state would be const [todos, setTodos] = useState([])
I hope it's clear what I mean. Typing this from the phone is not so easy :-)
Upvotes: 1