skip to Main Content

I need to make changes in the "todo" element based on the "todoId" of the object. How can I do it? Like, if I want to change only the "todo" array of the object that has the "todoId" = 1, for example.

const [todoList, setTodoList] = useState([
    {
      todoId: 0,
      todoName: "Personal",
      todo: []
    },
    {
      todoId: 1,
      todoName: "Work",
      todo: []
    },
    {
      todoId: 2,
      todoName: "College",
      todo: []
    },
  ])

5

Answers


  1. You need to essentially recreate the whole array.

    setTodoList((prev) => {
    let updateObject= prev.filter((item) => item.todoId===1)
    // do things to updateObject
    return ([...prev.filter((item) => item.todoId!==1), updateObject])
    })
    
    Login or Signup to reply.
  2. The updater function returned from the useState hook (setTodoList in your case) can be used with either of the following:

    • a value (for example, setTodoList([]) to set it to an empty array)
    • a callback that returns a value.

    The callback takes a single prevState argument which is just the previous state value. The callback should then return a new state value.

    This works great for array state variables since you will usually want to add/change/remove one item from the array.

    Here’s something you could try:

    function changeItem (itemId, newItem) {
        setTodoList(function (oldTodoList) {
            // create a new array without the item where item.todoId == itemId
            const itemsWithoutNewId = oldTodoList.filter((item) => item.id !== id);
            // add the new item and this array together and put it in another array
            const itemsWithNew = [...itemsWithoutNewId, newItem];
            // Return the final array
            return itemsWithNew;
        });
    }
    

    or as a one-liner (functionally equivalent):

    const changeItem = (id, newItem) => setTodoList(oldTodoList => [...oldTodoList.filter(item => item.todoId !== id), newItemForId]);
    

    They can both be used like this:

    const item = {
          todoId: 1,
          todoName: "New Todo",
          todo: []
    };
    changeItem(1, item);
    
    console.log(todoList);
    /*
    [
        {
          todoId: 0,
          todoName: "Personal",
          todo: []
        },
        {
          todoId: 1,
          todoName: "New Todo",
          todo: []
        },
        {
          todoId: 2,
          todoName: "College",
          todo: []
        },
    ]
    */
    
    Login or Signup to reply.
  3. You can create a function that receive the todoId and the todo data. and if finded the todo update the todo array.

    const update = (id, data) => {
      setTodoList((prevTodos) => {
        const index = prevTodos.findIndex((todo) => todo.todoId === id);
    
        if (index !== -1) {
          prevTodos[index].todo = prevTodos[index].todo.concat(data);
        }
    
        return prevTodos;
      });
    
      console.log(todoList);
    };
    
    Login or Signup to reply.
  4. Here is an example of how you could add and remove items from the various todo lists.

    You will need to use the function callback version when setting the state. Just clone the lists, find the one you want to add to (or remove from), and return the modified clone. When clicking on a button, the DOM is queried for data attributes to identify which list or item you are modifying.

    Note: In the example below, I just set a timestamp when adding a new item. You could modify this to show a dialog where you could enter the text of the todo item.

    Also, avoid using 0 for an ID. In most languages zero could mean: false, unknown, null, nil, unset, or undefined. Starting with 1 is preferred.

    const { useCallback, useEffect, useState } = React;
    
    const fetchTodos = () => Promise.resolve([
      { todoId: 1, todoName: "Personal", todo: [] },
      { todoId: 2, todoName: "Work",     todo: [] },
      { todoId: 3, todoName: "College",  todo: [] },
    ]);
    
    const TodoItem = ({ text, handleRemove }) => (
      <div className="TodoItem">
        <span>{text}</span>
        <button type="button" onClick={handleRemove}>Remove</button>
      </div>
    );
    
    const TodoList = ({ uid, name, items, handleAdd, handleRemove }) => (
      <div className="TodoList">
        <div className="heading">
          <h2>{name}</h2>
          <button type="button" data-uid={uid} onClick={handleAdd}>Add</button>
        </div>
        <ul>
          {items.map(({ text }, index) => (
            <li key={text} data-index={index}>
              <TodoItem text={text} handleRemove={handleRemove} />
            </li>
          ))}
        </ul>
      </div>
    );
    
    const App = () => {
      const [todoLists, setTodoLists] = useState([]);
      
      useEffect(() => {
        fetchTodos().then(setTodoLists);
      }, []);
      
      const handleAdd = useCallback((e) => {
        const { uid } = e.target.dataset;
        setTodoLists((currentTodoLists) => {
          const copy = structuredClone(currentTodoLists);
          const found = copy.find(({ todoId }) => todoId === +uid);
          if (found) {
            found.todo.push({ text: Date.now() });
            return copy;
          }
          return currentTodoLists;
        });
      }, []);
      
      const handleRemove = useCallback((e) => {
        const { index } = e.target.closest('li').dataset;
        const { uid } = e.target.closest('.TodoList').querySelector('.heading button').dataset;
        setTodoLists((currentTodoLists) => {
          const copy = structuredClone(currentTodoLists);
          const found = copy.find(({ todoId }) => todoId === +uid);
          if (found) {
            found.todo.splice(+index, 1);
            return copy;
          }
          return currentTodoLists;
        });
        
      }, []);
    
      return (
        <div className="App">
          <h1>Todos</h1>
          {todoLists.map(({ todoId, todoName, todo }) => (
            <TodoList
              key={todoId}
              uid={todoId}
              name={todoName}
              items={todo}
              handleAdd={handleAdd}
              handleRemove={handleRemove}
            />
          ))}
        </div>
      );
    };
    
    ReactDOM
      .createRoot(document.getElementById("root"))
      .render(<App />);
    h1, h2 { margin: 0; padding: 0; margin-bottom: 0.25rem; }
    
    .TodoList ul {
      display: flex;
      flex-direction: column;
      gap: 0.25rem;;
    }
    
    .TodoList .heading,
    .TodoItem {
      display: flex;
      flex-direction: row;
      align-items: center;
      gap: 0.5rem;
    }
    <div id="root"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
    Login or Signup to reply.
  5. Here’s a expanded example based on your data. The key section is in handleClick. After the section has been selected from the dropdown, the todo entered in the input box, and the button clicked…

    …we update the setTodoList state by taking the previous state and mapping over it. If the section name equals the value of the select state we spread the existing section object into a new object (no mutation!). We spread the section’s todos array into a new array, adding in a new todo with an id (based on the current length of the todo array). We finally return the updated section object. If the section name and the select value don’t match we return the section object without changes.

    (Note, for this example, I’ve changed the property key names in the data to be a little more meaningful.)

    const { useState } = React;
    
    function Example({ initial }) {
    
      // States for the list, the select, and the input
      const [ todoList, setTodoList ] = useState(initial);
      const [ select, setSelect ] = useState('Choose section');
      const [ input, setInput ] = useState('');
    
      // Update the input state
      function handleInput(e) {
        setInput(e.target.value);
      }
    
      // When the button is clicked
      function handleClick() {
      
        // `map` over the previous state
        setTodoList(prev => {
          return prev.map(section => {
          
            // If the select value equals the current iterated
            // section name
            if (section.name === select) {
    
              // Spread the existing section in a new object
              // then spread out the existing section todos array
              // into a new array adding in a new todo object with its
              // own id
              return {
                ...section,
                todos: [
                  ...section.todos,
                  { id: section.todos.length + 1, text: input }
                ]
              };
            }
            
            // Otherwise just return the section
            return section;
          });
        });
      }
    
      // Update the select
      function handleSelect(e) {
        if (e.target.value !== 'Choose section') {
          setSelect(e.target.value);
        }
      }
    
      // Disabled the button if either the select or input values
      // are "empty"
      function isButtonDisabled() {
        return select === 'Choose section' || input === '';
      }
    
      return (
        <main>
          <section>
            <select value={select} onChange={handleSelect}>
              <option disabled>Choose section</option>
              {todoList.map(section => {
                return <Option key={section.id} option={section} />
              })}
            </select>
            <input type="text" onChange={handleInput} />
            <button
              type="button"
              disabled={isButtonDisabled()}
              onClick={handleClick}
            >Add todo
            </button>
          </section>
          <section>
            {todoList.map(section => {
              return (
                <TodoSection
                  key={section.id}
                  name={section.name}
                  todos={section.todos}
                />
              );
            })}
          </section>
        </main>
      );
    
    }
    
    function Option({ option }) {
      return (
        <option
          value={option.name}
        >{option.name}
        </option>
      );
    }
    
    function TodoSection({ name, todos }) {
      return (
        <section className="section">
          <header>{name}</header>
          <ul>
            {todos.map(todo => {
              return <Todo key={todo.id} todo={todo} />
            })}
          </ul>
        </section>
      );
    }
    
    function Todo({ todo }) {
      return (
        <li>
          {todo.text}
        </li>
      );
    }
    
    const todoList=[{id:0,name:"Personal",todos:[]},{id:1,name:"Work",todos:[]},{id:2,name:"College",todos:[]}];
    
    const node = document.getElementById('root');
    const root = ReactDOM.createRoot(node);
    root.render(<Example initial={todoList} />);
    main > section ~ section { margin-top: 1rem; }
    .section header { font-weight: 600; }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.min.js"></script>
    <div id="root"></div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search