caribbean
caribbean

Reputation: 317

Mapping over array of objects returns undefined

in React app I'm trying to increment +1 to the id field of each object in the array.

function App() {
  const[todos, setTodos] = useState([
    {
      id: 1,
      title: 'Take out the trash',
      completed: false
    },
    {
      id: 2,
      title: 'Dinner',
      completed: false
    },
    {
      id: 3,
      title: 'Meeting with boss',
      completed: false
    }
  ]);

  function onClick(){
    setTodos(todos => todos.map(todo => todo.id = todo.id + 1))
  }

  return (
    <div className="App">
      <div className="todo-list">
        <ul>
          {todos.map(todo => <li>{todo.id},{todo.title},{String(todo.completed)}</li>)}
        </ul>
        <button onClick={()=>onClick()}
        >Update
        </button>
      </div>
    </div>
  );
} 

On the initial rendering of the page list of objects is displayed properly. On first click of the button they change to this:

,,undefined
,,undefined
,,undefined

On 3rd click to this error:

TypeError: Cannot create property 'id' on number '2'

did I miss something about the map function ?

Upvotes: 0

Views: 638

Answers (4)

Max
Max

Reputation: 2036

You actually don't return new todo entity in there, but just return id property, that's is a bug. Here's is a fix:

  function onClick(){
    setTodos(todos => todos.map(todo => ({ ...todo, id: todo.id + 1 })))
  }

Upvotes: 1

JMadelaine
JMadelaine

Reputation: 2964

function onClick(){
  setTodos(todos => todos.map(todo => todo.id = todo.id + 1))
 }

You're returning the todo.id from your map function, therefore you're setting todos as an array of ids [2, 3, 4]. Then when you try to access todo.id, it is undefined as number.id is undefined`.

You're also mutating state by saying todo.id = .... If you find yourself using the assignment oprerator (=) when setting state, you may be doing something wrong. You should never have state = ....

You want to do something like this:

function onClick(){
  setTodos(prev => prev.map(t => ({ ...t, id: t.id + 1 })))
 }

I've also renamed todos to prev as you're creating a shadowed variable name.

Upvotes: 4

tony95
tony95

Reputation: 180

Your map function not return a object but I think you should use the reduce JavaScript function

function onClick() {
    const reducerFunction = (r, v) => r.concat({ ...v, id: v.id + 1 })
    setTodos(
      todos.reduce(reducerFunction, [])
    );
}

I also think you should try the useReduce Hooks, in this case generate solutions more clean.

An alternative to useState. Accepts a reducer of type (state, action) => newState, and returns the current state paired with a dispatch method. (If you’re familiar with Redux, you already know how this works.)

useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. useReducer also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks.

Upvotes: 0

Muhammad Hassan Khan
Muhammad Hassan Khan

Reputation: 149

Map function iterate to each value and return the mapped value you computed in the iterator.

Your code converts array of todo objects to array of incremented todo ids like [2,3,4] so when next your map method runs it gives the error you are facing. As first time your map has converted objects to integers.

You should update your map method something like this: todos.map(todo => ({...todo, id: todo.id + 1}))

Note: Also you don’t need to write todos => in your setTodos hook.

Upvotes: 1

Related Questions