mr_nocoding
mr_nocoding

Reputation: 487

React Hooks, useState related question. How to update one property of object and keep the rest

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

Answers (4)

3limin4t0r
3limin4t0r

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

Paveloosha
Paveloosha

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

Mohammad Faisal
Mohammad Faisal

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

CertainPerformance
CertainPerformance

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

Related Questions