'React - How to avoid object getting same key? - Todo

I'm working on a todo app. The problem is: when I add two todos for example, first one gets id 0 and the other one gets id 1 (this is from todo.length), if I remove the first one (id 0) and add another one, it will create another todo with id1 which already exist. How do I prevent this?

I'm thinking maybe useEffect that is checking/updating the todo so it always the correct length. But i'm not really sure how to implement this (i'm still learning), advice would be appreciated

This is the code & sandbox link so you can test code https://codesandbox.io:

import React, {useState} from "react";


export default function App() {
  const [todo, setTodo] = useState([])
  const [description, setDescription] = useState("")

  function handleDescriptionChange(event) {
    setDescription(event.target.value)
  }

  function handleFormSubmit(event) {
    event.preventDefault()

    setTodo([...todo, {
      id: todo.length,
      task: description,
      complete: false,
    }])
    setDescription("")
  }

  function handleDeleteButton(id) {
    setTodo(todo.filter(todo => todo.id !== id))
  }

  return (
    <div className="App">
      <h1>Todo App</h1>
      <form onSubmit={handleFormSubmit}>
        <label htmlFor="todo">Add todo: </label>
        <input type="text" value={description} onChange={handleDescriptionChange} required/>
        <button type="submit">Submit</button>
      </form>
      <ul className="todo-list">
        {todo.map((todo, index) =>
        <li className="todo-item" key={todo.length}>Todo {todo.id}: {todo.task} {todo.complete.toString()}
        <button onClick={() => handleDeleteButton(todo.id)}>Remove</button></li>)}
      </ul>
    </div>
  );
}

Have a nice one guys



Solution 1:[1]

Use something else instead of the length as id. Create a separate state variable counter (which you will increase each time you assign it to id) for example and use that, or use library like this: shortid.


Also I think you have a typo and you meant todo.id instead of todo.length here:

key={todo.length}

Solution 2:[2]

React key works differently , as you are specifying id as the length of todo you should aspect that id should always return unique values always and the todo list you are mapping needs a unique key so that react can change its state and re rerender only if the key is changed but in your case react rerenders every time you add or delete todos that's not good approach as you can use Nano Id or generate unique or random token and use a react fragment for passing key id.

    import React, {useState} from "react";
    
    
    export default function App() {
      const [todo, setTodo] = useState([])
      const [description, setDescription] = useState("")
      model.id = nanoid() 
      function handleDescriptionChange(event) {
        setDescription(event.target.value)
      }
    
      function handleFormSubmit(event) {
        event.preventDefault()
    
        setTodo([...todo, {
          id: model.id,
          task: description,
          complete: false,
        }])
        setDescription("")
      }
    
      function handleDeleteButton(id) {
        setTodo(todo.filter(todo => todo.id !== id))
      }
    
      return (
        <div className="App">
          <h1>Todo App</h1>
          <form onSubmit={handleFormSubmit}>
            <label htmlFor="todo">Add todo: </label>
            <input type="text" value={description} onChange={handleDescriptionChange} required/>
            <button type="submit">Submit</button>
          </form>
          <ul className="todo-list">
            {todo.map((todo, index) =>
<React.Fragment>
            <li className="todo-item" key={todo.length}>Todo {todo.id}: {todo.task} {todo.complete.toString()}
            <button onClick={() => handleDeleteButton(todo.id)}>Remove</button></li>
<React.Fragment>
)}
          </ul>
        </div>
      );
    }

Solution 3:[3]

Always use a unique id for key

Well, you can use key={index}

<ul className="todo-list">
        {todo.map((todo, index) =>
        <li className="todo-item" key={index}>Todo {todo.id}: {todo.task} {todo.complete.toString()}
        <button onClick={() => handleDeleteButton(todo.id)}>Remove</button></li>)}
</ul>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Build Though
Solution 3 Ali Abbas