I’m trying to make a simple Todo list app. I’m trying to use the push
method to add to the array in JavaScript. I have a button that adds a todo and an input to enter the todo. Then an if
/else
statement determines whether or not the value of the todo input is null
or an empty string. If the todo input is not null
or an empty string then the following code is executed:
todos.push(uploadTodo)
setTodo(todos)
console.log(todos)
This is where my problem is. todos.push(uploadTodo)
will only add one new item to my array. After that it replaces the last element in the array.
Here’s my code:
import { useState } from 'react';
import Todo from './Todo'
function App() {
let todos = ["Test", "testing"]
const [todo, setTodo] = useState(todos)
const [value, setValue] = useState(null)
const getInput = (event) => {
setValue(event.target.value)
}
const pushTodo = (uploadTodo) => {
if (value === null || value === "") {
alert("Can't add an empty todo.")
}
else {
todos.push(uploadTodo)
setTodo(todos)
console.log(todos)
}
}
return (
<>
<h1>Todo List!</h1>
<Todo></Todo>
<button onClick={() => pushTodo(value)}>Add a todo</button><button>Clear todos</button><br></br>
<input placeholder="Add a todo" onChange={getInput}></input>
</>
);
}
export default App;
I’m using React 18.2.0.
4
Answers
Direct way can be
setTodo([…todos, uploadTodo])
Indirect way can be
setTodo(todos.join(uploadTodo).split(""))
or
setTodo([…todos.join(uploadTodo).split("")])
as join() method does not change the original array.
It seems you’re trying to mutate the state directly by using push method instead updating the state.
Since the state is an array, you can use Array.concat method to update it:
This will return a new array and set it to the state.
Updated
Every time your component re-renders, your local
todos
variable gets reset to its original value by this line:You then push the new value:
Now
todos
is["Test", "Testing", uploadTodo]
and you update state:This triggers a re-render, and
todos
gets reinitialized:So each time you push a value, you’re pushing into the original list again.
The easiest solution is to move the
todos
value out of the component so it doesn’t get reinitialized on every render:But I would also strongly recommend you update state with a new array instead of continually pushing into the original
todos
one.Calling a state update and passing in the same array that’s already in state–even if the items in the array have changed–won’t trigger a re-render, because React sees that the new array is the same array it already has, and this can (obviously) cause the app to behave in strange ways.
Instead of
todos.push(...)
I’d recommend you create a new array each time by spreading the existing state and appending the new value:In React, all stateful data must be immutable. That means you cannot change existing values and instead you must create new ones. Here’s a minimal, verifiable example. Run the example below and create a few todos. Then get to work on completing them!
You should also be aware that
setState
is asynchronous. This means that the value of state will not be available immediately after thesetState
call. The new state is only available during the subsequent render. Read the Component State FAQ for details.