简体   繁体   中英

Making state updates more readable in React

I'm practicing React by making a simple todo list with a task and a counter next to the task to indicate how many times I need to do it.

I would like to make the state update more readable by creating an object which contains the values that I want to update rather than just updating everything inline which can be kind of tricky to read, at least for me. However, it's not showing the item name or the counter isn't increasing on button click.

Here's the code sandbox: https://codesandbox.io/s/todo-list-practice-5g45c?file=/src/index.js

Original approach:
   const duplicates = () => {
     if (!(input in items)) {
       setListItem({ ...items, [input]: 1 });
     } else {
       setListItem({ ...items, [input]: items[input] + 1 });
     }
   };

Refactored approach: 

  const checkDuplicate = () => {
    if (!(input in items)) {
      const newItem = {
        name: input,
        count: 1
      };
      setItems({ ...items, newItem });
    } else {
      setItems((items[input] += 1));
    }
  };

Button and ul

      <button
        onClick={() => {
          checkDuplicate();
        }}
      >
        add item
      </button>

      <ul>
        {Object.keys(items).map((el) => (
          <li>
            {el.name}: {items[el].count}
          </li>
        ))}
      </ul>

There's a lot of different ways to handle this, but this is one way that seems to work well for me:

编辑 kind-rain-hlzwq

function Todo() {
  const [newItem, setNewItem] = useState("");
  const [items, setItems] = useState({});

  const handleAddItem = () => {
    const count = items[newItem] || 0;

    setItems({
      ...items,
      [newItem]: count + 1,
    });
  };

  return (
    <div>
      <input
        onChange={(event) => {
          setNewItem(event.target.value);
        }}
        value={newItem}
      />
      <button onClick={handleAddItem}>Add Item</button>
      <ul>
        {Object.entries(items).map(([item, count]) => {
          return (
            <li key={item}>
              {item}: {count}
            </li>
          );
        })}
      </ul>
    </div>
  );
}

Most of the difference between my code and yours are irrelevant, just personal preferences. I do see a few issues in your checkDuplicate function, though:

const checkDuplicate = () => {
  if (!(input in items)) {
    const newItem = {
      name: input,
      count: 1,
    };
    setItems({ ...items, newItem });
  } else {
    setItems((items[input] += 1));
  }
};

Here, you're using setItems to set it to an object that contains everything from the old object, but adding a new key/value pair. The key in your code is newItem and the value is { name: input, count: 1 } .

I don't think you want the key to be newItem , I think you want it to be input , or something else at least.

Maybe this?

setItems({ ...items, [input]: newItem });

And then in your else clause, you have:

setItems((items[input] += 1));

This calls setItems with the return value of (items[input] += 1) , which is almost certainly not what you're going for. Instead, I think you want to do basically the same thing you're doing in your if clause, but instead just increment the count of the one that has the duplicate key .

Maybe this?

setItems({
  ...items,
  [input]: {
    ...items[input],
    count: items[input].count + 1,
  },
});

There are other ways to do that sort of thing, but the important thing is to not mutate items when you increment count ; you'll need to construct a whole new object that includes the incremented count .

Lastly, if you look at my code's <li> , you'll see I added a key prop to it. This is not your issue, but it's worth reading about and including in your code when you map over lists to generate JSX like you're doing. Read about React Keys and Lists here .

Definitely take my code with a grain of salt. I don't have access to all of your code, and so I put something together quickly to try to help unblock you. I left out some stuff that you had, like the name key in your items object.

Good luck!

import ReactDOM from "react-dom";
import React, { useState } from "react";

// TODO: build a todo app which can:
//  1. add items to a list (render below input)
//  2. iterate a counter next to each item

// We should use arrays for this use case, there's no restriction of using plain object
const Todo = () => {
  const [items, setItems] = useState([]);
  const [input, setInput] = useState("");

  const checkDuplicate = () => {
    const index = items.findIndex((item) => item.name === input);

    // if the item is already in array we get its index
    if (index !== -1) {
      const newArr = [...items];
      newArr[index].count += 1;
      return setItems(newArr);
    }

    // if the item is not in an array already we append it
    setItems([
      ...items,
      {
        name: input,
        count: 1
      }
    ]);
  };

  return (
    <div>
      <h1>TODO LIST</h1>
      <input value={input} onChange={(e) => setInput(e.target.value)} />
      <button
        onClick={() => {
          checkDuplicate();
        }}
      >
        add item
      </button>
      <ul>
        {items.map((el) => (
          <li>
            {el.name}: {el.count}
          </li>
        ))}
      </ul>
    </div>
  );
};
ReactDOM.render(<Todo />, document.getElementById("root"));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM