Reputation: 487
I want to do a to do list with hooks. Here is my related code.
function generateId() {
return '_' + Math.random().toString(36).substr(2, 9);
};
export function TodoList(){
const [todos, setTodos] = React.useState([])
const [input, setInput] = React.useState('')
const [time, setTime] = React.useState('')
const handleSubmit = () => {
setTodos((todos) => todos.concat({
text: input,
id: generateId(),
timeRequired: time,
}))
setInput('')
setTime('')
}
// remove to do works fine.
const removeTodo = (id) => setTodos((todos) => todos.filter((todo) => todo.id !== id ))
/// confusion here
let todo = (id) => todos.find(x => x.id = id)
const decrement = (id) => setTodos((todo(id).timeRequired) => timeRequired - 1)
///
return(
<React.Fragment>
<input type="text" value={input} placeholder="New Task" onChange={(e) => setInput(e.target.value)}>
</input>
<input type="number" value={time} placeholder="Hours Required" onChange={(e) => setTime(e.target.value)}>
</input>
<button onClick={handleSubmit}> Submit </button>
<ul>
{todos.map(({text, id, timeRequired}) => (
<li key={id}>
<span>{text}: remaining {timeRequired} hours </span>
<button onClick={() => removeTodo(id)}>
Cancel ❌
</button>
<button onClick={() => decrement(id)}> Decrement ➖ </button>
<button > Increase ➕ </button>
<button> Done ✔️ </button>
</li>
))}
</ul>
</React.Fragment>
)
}
So I want to increment/decrement the time remaining on the todo list. However, I don't know how to choose the specific item in the list and change one property ( time remaining) and keep the text property.
Thanks.
Upvotes: 2
Views: 304
Reputation: 21110
Here is how the decrement
function might look. This takes the id
, loops over all todos
. If a todo
doesn't match the provided id
leave it as is. If it does match create a shallow copy of the object ({...todo}
) and override the timeRequired
property with an updated value.
const decrement = (id) => setTodos((todos) => todos.map((todo) => {
if (todo.id != id) return todo;
return {...todo, timeRequired: todo.timeRequired - 1};
}));
Note that the order of {...todo, timeRequired: todo.timeRequired - 1}
is important. By placing timeRequired: todo.timeRequired - 1
after ...todo
it will override the existing property. Just like the object {a: 1, b: 2, a: 3}
will leave you with the object {a: 3, b: 2}
, because the second definition of the key a
overrides the first one.
Upvotes: 3
Reputation: 623
Try this...
const removeTodo = (id) => setTodos((todos) => todos.filter((todo) => todo.id !== id ))
const decrement = (id) => setTodos((todos) => todos.map((todo) => {
const {
id: oldID,
timeRequired,
} = todo;
return {
...todo,
timeRequired: id===oldID? timeRequired-1 : timeRequired,
};
}));
Upvotes: 1
Reputation: 2363
You can try it like this
const decrement = (id) => {
const targetTodo = todos.find(x => x.id == id); //get the todo
const currentTime = targetTodo.timeRequired -1 //decrease the time
const newTodo = { ...targetTodo , timeRequired : currentTime } // create new todo object
const newTodoList = todos.map( item => { // finally replace the old todo object
if(item.id = id) return newTodo
else return item;
})
setTodos(newTodoList)
}
Upvotes: 1
Reputation: 370679
Once you identify the element in the array, create a new object from the todo with a decremented timeRequired
property. Then use .slice
to slice the array before and after the new item, with the new item in the middle:
const decrement = (i) => {
const replacedTodo = {
...todos[i],
timeRequired: todos[i].timeRequired - 1,
};
setTodos(
todos.slice(0, i),
replacedTodo,
todos.slice(i + 1),
);
};
{todos.map(({ text, id, timeRequired }, i) => (
// ...
<button onClick={() => decrement(i)}> Decrement ➖ </button>
It'll be easier to use the index from the .map
function directly rather than to find the matching element from the ID later.
Upvotes: 1