skip to Main Content

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


  1. 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.

    Login or Signup to reply.
  2. 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:

    setTodo(todos.concat(uploadTodo))
    

    This will return a new array and set it to the state.

    Updated

    setTodo(todos.concat([uploadTodo]))
    
    Login or Signup to reply.
  3. Every time your component re-renders, your local todos variable gets reset to its original value by this line:

    let todos = ["Test", "testing"]
    

    You then push the new value:

    todos.push(uploadTodo)
    

    Now todos is ["Test", "Testing", uploadTodo] and you update state:

    setTodo(todos)
    

    This triggers a re-render, and todos gets reinitialized:

    let todos = ["Test", "testing"]
    

    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:

    const todos = ["Test", "testing"]
    
    function App() {
      const [todo, setTodo] = useState(todos)
      const [value, setValue] = useState(null)
      // ...
    

    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:

    setTodo([...todo, uploadTodo])
    
    function App() {
      const [todo, setTodo] = useState(["Test", "testing"])
      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 {
          // create a new array with the exiting state plus the new value
          setTodo([...todo, uploadTodo])
        }
      }
    
      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>
        </>
      );
    }
    
    Login or Signup to reply.
  4. 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 the setState call. The new state is only available during the subsequent render. Read the Component State FAQ for details.

    function App() {
      const [todos, setTodos] = React.useState([])
      const [todo, setTodo] = React.useState("")
      function onChange(event) {
        setTodo(event.currentTarget.value)
      }
      function onSubmit(event) {
        event.preventDefault()
        setTodos([...todos, todo]) // create a *new* array!
        setTodo("")
      }
      return <form onSubmit={onSubmit}>
        <ul>
          {todos.map((t, key) => <li key={key}>{t}</li>)}
        </ul>
        <input value={todo} onChange={onChange} placeholder="Enter a todo.." />
      </form>
    }
    
    ReactDOM.createRoot(document.querySelector("#app")).render(<App />)
    <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <div id="app"></div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search