在useState中更新数组内字典中的计数。

huangapple go评论57阅读模式
英文:

Updating count in dictionary inside array in useState

问题

在Next.js中,我创建了一个useState的提供者,现在我可以将项目添加到购物车中,但这就是我能做的全部...

cart = [{ product: "产品名称", count: 1 }]

当我想要添加一个项目到这个购物车时,我可以这样做:

setCart([...cart, { product: '新的项目', count: 1 }])

但我就是不能编写代码来更新购物车中现有项目的计数...

我不知道该怎么做...

英文:

In nextJs I created a provider for useState, now I am able to add items to the cart, but that's all that I can do...

cart = [{product: "product name", count:1}]

When I want to add an item to this I can do

setCart([...cart, {product:'new one', count:1}])

But I am just not able to write code that will update the count of existing items in the cart...

I don't know how...

答案1

得分: 1

你可能最好使用useReducer而不是useState,因为你的模型比较复杂。尝试像这样做:

function cartReducer(oldCart, action) {
    switch (action.type) {
        case 'new':
            // 我们没有像id这样的唯一标识,所以要防止名称冲突
            if (oldCart.some(item => item.name === action.name)) return oldCart;
            return [...oldCart, { name: action.name, count: 1 }];
        case 'increment':
            return oldCart.map((item) => item.name === action.name ? { ...item, count: item.count + 1 } : item);
        case 'decrement':
            return oldCart.map((item) => item.name === action.name ? { ...item, count: item.count - 1 } : item);
        default:
            return oldCart
    }
}

function YourComponent(props) {
    const [cart, dispatch] = useReducer(cartReducer, []);
    function createItem(name) {
        dispatch({ type: 'new', name });
    }

    function incrementItemCount(item) {
        dispatch({ type: 'increment', name: item.name });
    }

    function decrementItemCount(item) {
        dispatch({ type: 'decrement', name: item.name });
    }
    // 渲染逻辑在此处
}

然而,如果你想以这种方式处理,useState有一种形式,允许你获取旧版本的状态

英文:

You're probably better off using useReducer vs. useState, due to the complexity of your model. Try something like this:

function cartReducer(oldCart, action) {
    switch(action.type) {
        case 'new':
            // we don't have anything unique like id, so prevent collisions on name
            if (oldCart.some(item => item.name === action name)) return oldCart;
            return [...oldCart, {name: action.name, count: 1}];
        case 'increment':
            return oldCart.map((item) => item.name === action.name ? {...item, count: item.count + 1} : item);
        case 'decrement':
            return oldCart.map((item) => item.name === action.name ? {...item, count: item.count - 1} : item);
        default:
            return oldCart
    }
}

function YourComponent(props) {
    const [cart, dispatch] = useReducer(cartReducer, []);
    function createItem(name) {
        dispatch({type: 'new', name});
    }

    function incrementItemCount(item) {
        dispatch({type: 'increment', name: item.name});
    }

    function decrementItemCount(item) {
        dispatch({type: 'decrement', name: item.name});
    }
    //render logic here
}

However, useState has a form that lets you get at the old version of the state if you want to do it that way.

答案2

得分: 0

你不能简单地将新项目添加到购物车中。你需要检查现有的购物车并进行相应的更新。

在下面的React演示中,你可以点击按钮将商品添加到购物车中。

const { useCallback, useEffect, useState } = React;

const fetchProducts = Promise.resolve([
  { name: 'Apple' }, { name: 'Banana' }, { name: 'Pear' }
]);

const App = () => {
  const [products, setProducts] = useState([]);
  const [cart, setCart] = useState([]);

  const addToCart = useCallback((e) => {
    const product = e.target.textContent;
    setCart((existingCart) => {
      const cartCopy = structuredClone(existingCart);
      let existing = cartCopy.find((item) => item.product === product);
      if (existing) {
        existing.count += 1;
      } else {
        cartCopy.push({ product, count: 1 });
      }
      return cartCopy;
    });
  }, []);

  useEffect(() => {
    fetchProducts.then(setProducts);
  }, []);

  return (
    <div>
      <h2>Products</h2>
      {products.map(({ name }) => (
        <button key={name} onClick={addToCart}>{name}</button>
      ))}
      <h2>Cart</h2>
      <ul>
        {cart.map(({ product, count }) => (
          <li key={product}>{`${product} × ${count}`}</li>
        ))}
      </ul>
    </div>
  );
};

ReactDOM
  .createRoot(document.getElementById("root"))
  .render(<App />);
<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>
英文:

You cannot just spread the new item. You need to check the existing cart and update according.

In the React demo below, you can click the buttons to add the items to the cart.

<!-- begin snippet: js hide: false console: true babel: true -->

<!-- language: lang-js -->

const { useCallback, useEffect, useState } = React;
const fetchProducts = Promise.resolve([
{ name: &#39;Apple&#39; }, { name: &#39;Banana&#39; }, { name: &#39;Pear&#39; }
]);
const App = () =&gt; {
const [products, setProducts] = useState([]);
const [cart, setCart] = useState([]);
const addToCart = useCallback((e) =&gt; {
const product = e.target.textContent;
setCart((existingCart) =&gt; {
const cartCopy = structuredClone(existingCart);
let existing = cartCopy.find((item) =&gt; item.product === product);
if (existing) {
existing.count += 1;
} else {
cartCopy.push({ product, count: 1 });
}
return cartCopy;
});
}, []);
useEffect(() =&gt; {
fetchProducts.then(setProducts);
}, []);
return (
&lt;div&gt;
&lt;h2&gt;Products&lt;/h2&gt;
{products.map(({ name }) =&gt; (
&lt;button key={name} onClick={addToCart}&gt;{name}&lt;/button&gt;
))}
&lt;h2&gt;Cart&lt;/h2&gt;
&lt;ul&gt;
{cart.map(({ product, count }) =&gt; (
&lt;li key={product}&gt;{`${product} &#215;${count}`}&lt;/li&gt;
))}
&lt;/ul&gt;
&lt;/div&gt;
);
};
ReactDOM
.createRoot(document.getElementById(&quot;root&quot;))
.render(&lt;App /&gt;);

<!-- language: lang-html -->

&lt;div id=&quot;root&quot;&gt;&lt;/div&gt;
&lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js&quot;&gt;&lt;/script&gt;

<!-- end snippet -->

答案3

得分: 0

因为 React 使用 Object.is 来比较相等性,我们需要确保传递给 setState 的新对象具有不同的引用。

我会用一个小型自定义 hook 来实现这个。它可能看起来像这样:

function useStateArray<T>(initialVal: T[]) {
  const [arr, setArr] = useState(initialVal);

  const push = React.useCallback(
    (val: T) => setArr((old) => [...old, val]),
    []
  );
  const edit = React.useCallback((val: Partial<T>, index: number) => {
    setArr((old) => {
      const newArr = [...old];
      newArr[index] = { ...newArr[index], ...val };
      // 我们需要返回一个新数组以更新状态!
      return newArr;
    });
  }, []);

  return [arr, push, edit, setArr];
}

这个想法是,如果你在 useState 中经常使用数组,可能有几个辅助函数是有意义的。

然后你可以添加一些其他实用函数(比如删除、弹出等)。

英文:

Because React uses Object.is to compare equality we need to ensure that the new object we pass to setState has a different reference.

I'd do this with a little custom hook. It could look something like so:

function useStateArray&lt;T&gt;(initialVal: T[]) {
  const [arr, setArr] = useState(initialVal);

  const push = React.useCallback(
    (val: T) =&gt; setArr((old) =&gt; [...old, val]),
    []
  );
  const edit = React.useCallback((val: Partial&lt;T&gt;, index: number) =&gt; {
    setArr((old) =&gt; {
      const newArr = [...old];
      newArr[index] = { ...newArr[index], ...val };
      // We need to return a new array to update the state!
      return newArr;
    });
  }, []);

  return [arr, push, edit, setArr];
}

The idea being that if you're using arrays in your useState a lot it might make sense to have a few helper functions ready to go.

You could then add some other utility functions (like delete, pop, etc).

huangapple
  • 本文由 发表于 2023年7月10日 21:30:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/76654251.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定