useCallback 不更新包含对象的数组

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

useCallback not updating array with object

问题

我正在尝试使用useCallback更新包含对象的数组,但它似乎不是在更新对象,而是在添加或追加一个对象。

const items = [
    {
        id: 101,
        name: 'Mae Jemison',
    },
    {
        id: 201,
        name: 'Ellen Ochoa',
    },
];
const [headings, setHeadings] = useState(items);

const handleHeadingTextChange = useCallback(
    (value, id) => {
        let items2 = headings;
        items2 = items2.map((item, key) => {
            if (items2[key].id === id) {
                items2[key].name = value;
            }
            return items2;
        });

        setHeadings((prevState) => [...prevState, items2]) // 添加,而不是更新
        //setHeadings((prevState) => [...prevState, ...items2]) // 尝试2:仍然在添加
    },
    [setHeadings],
);

<input type="text" id="101" value="" onChange={handleHeadingTextChange} />

所以,预期的输出是在更改后:

items = [
    {
        id: 101,
        name: 'Johaan',
    },
    {
        id: 201,
        name: 'Ellen Ochoa',
    },
];

但实际上我得到的是:

items = [
    {
        id: 101,
        name: 'Johaan',
    },
    {
        id: 201,
        name: 'Ellen Ochoa',
    },
    [
        {
            id: 101,
            name: 'Johaan',
        },
        {
            id: 201,
            name: 'Ellen Ochoa',
        },
    ],
];

我不确定如何在setHeading函数中设置值,以便它只更新值而不是追加一个。是否有办法使其仅进行更新?

英文:

I am trying to update array with objects using useCallback but instead of updating object it is adding or appending one too.

const items = [
       {
           id: 101,
           name: &#39;Mae Jemison&#39;,
       },
       {
           id: 201,
           name: &#39;Ellen Ochoa&#39;,
       },
   ];
const [headings, setHeadings] = useState(items);

const handleHeadingTextChange = useCallback(
       (value, id) =&gt; {
           let items2 = headings;
           items2 = items2.map((item, key) =&gt; {
               if (items2[key].id == id) {
                   items2[key].name = value
               }
               return items2
           });

           setHeadings((prevState) =&gt; [...prevState, items2]) // adding, not updating
           //setHeadings((prevState) =&gt; [...prevState, ...items2]) // try 2 : still adding
       },
       [setHeadings],
   );

&lt;input type=&quot;text&quot; id=&quot;101&quot; value=&quot;&quot; onChange={handleHeadingTextChange} /&gt;

So, on change expected output is

items = [
        {
            id: 101,
            name: &#39;Johaan&#39;,
        },
        {
            id: 201,
            name: &#39;Ellen Ochoa&#39;,
        },
    ];

But Instead I am getting

items = [
        {
            id: 101,
            name: &#39;Johaan&#39;,
        },
        {
            id: 201,
            name: &#39;Ellen Ochoa&#39;,
        },
       [{
            id: 101,
            name: &#39;Johaan&#39;,
        },
        {
            id: 201,
            name: &#39;Ellen Ochoa&#39;,
        }]
    ];

I am not sure how to set value in setHeading function so that it only update the value and not appending one too. Is there way to make it update only?

答案1

得分: 1

问题

  1. .map 遍历整个数组。如果你只想更新 单个 项,这就没有意义。
  2. onChange 接受一个事件处理程序,但你的处理程序接受 (value, id)
  3. &lt;input&gt;value 属性应该从对象的 name 属性设置

解决方案

function MyComponent() {
  const [headings, setHeadings] = useState(items);

  const updateName = key => event => {
    setHeadings(prevState => [
      // 要更新的键前的元素
      ...prevState.slice(0, key),

      // 要更新的元素
      { ...prevState[key], name: event.currentTarget.value },

      // 键后的元素
      ...prevState.slice(key + 1),
    ])
  }

  return headings.map((h, key) =>
    <input key={key} id={h.id} value={h.name} onChange={updateName(key)} />
  )
}

改进

想象一下每次都要编写这个函数,当你有一个带有数组或对象状态的组件时。将其提取为通用函数,根据需要在其他地方使用 -

// 通用函数
function arrUpdate(arr, key, func) {
  return [...arr.slice(0, key), func(arr[key]), ...arr.slice(key + 1)]
}

function objUpdate(obj, key, func) {
  return { ...obj, [key]: func(obj[key]) }
}

// 简化的组件
function MyComponent() {
  const [headings, setHeadings] = useState(items);

  const updateName = key => event => {
    setHeadings(prevState =>
      // 在键处更新数组
      arrUpdate(prevState, key, elem =>
        // 更新元素的name属性
        objUpdate(elem, "name", prevName =>
          // 新元素的name
          event.currentTarget.value
        )
      )
    )
  }

  return headings.map((h, key) =>
    <input key={key} id={h.id} value={h.name} onChange={updateName(key)} />
  )
}

多个箭头函数?

柯里化函数使得编写事件处理程序更容易,但也许你以前从未使用过它们。这是未柯里化版本的样子 -

function MyComponent() {
  const [headings, setHeadings] = useState(items);

  const updateName = (key, event) => {
    setHeadings(prevState =>
      // 在键处更新数组
      arrUpdate(prevState, key, elem =>
        // 更新元素的name属性
        objUpdate(elem, "name", prevName =>
          // 新元素的name
          event.currentTarget.value
        )
      )
    )
  }

  return headings.map((h, key) =>
    <input key={key} id={h.id} value={h.name} onChange={e => updateName(key, e)} />
  )
}
英文:

problems

  1. .map iterates over the entire array. that doesn't make sense if you are trying to just update a single item.
  2. onChange takes an event handler, but your handler accepts (value, id)
  3. the value property of your &lt;input&gt; should be set from the object's name property

solution

function MyComponent() {
  const [headings, setHeadings] = useState(items);

  const updateName = key =&gt; event =&gt; {
    setHeadings(prevState =&gt; {
      return [
        // elements before the key to update
        ...prevState.slice(0, key),

        // the element to update
        { ...prevState[key], name: event.currentTarget.value },

        // elements after the key
        ...prevState.slice(key + 1),
      ]
    })
  }

  return headings.map((h, key) =&gt;
    &lt;input key={key} id={h.id} value={h.name} onChange={updateName(key)} /&gt;
  )
}

improvement

Imagine having to write that function each time you have a component with array or object state. Extract it to a generic function and use it where necessary -

// generic functions
function arrUpdate(arr, key, func) {
  return [...arr.slice(0, key), func(arr[key]), ...arr.slice(key + 1)]
}

function objUpdate(obj, key, func) {
  return {...obj, [key]: func(obj[key])}
}

// simplified component
function MyComponent() {
  const [headings, setHeadings] = useState(items);

  const updateName = key =&gt; event =&gt; {
    setHeadings(prevState =&gt;
      // update array at key
      updateArr(prevState, key, elem =&gt;
        // update elem&#39;s name property
        updateObj(elem, &quot;name&quot;, prevName =&gt;
          // new element name
          event.currentTarget.value
        )
      )
    )
  }

  return headings.map((h, key) =&gt;
    &lt;input key={key} id={h.id} value={h.name} onChange={updateName(key)} /&gt;
  )
}

multiple arrow functions?

Curried functions make writing your event handlers easier, but maybe you've never used them before. Here's what the uncurried version would look like -

function MyComponent() {
  const [headings, setHeadings] = useState(items);

  const updateName = (key, event) =&gt; {
    setHeadings(prevState =&gt;
      // update array at key
      updateArr(prevState, key, elem =&gt;
        // update elem name property
        updateObj(elem, &quot;name&quot;, prevName =&gt;
          // new element name
          event.currentTarget.value
        )
      )
    )
  }

  return headings.map((h, key) =&gt;
    &lt;input key={key} id={h.id} value={h.name} onChange={e =&gt; updateName(key, e)} /&gt;
  )
}

huangapple
  • 本文由 发表于 2023年2月10日 02:50:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/75403151.html
匿名

发表评论

匿名网友

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

确定