Reputation: 317
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
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
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
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
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