Как правильно удалить элемент из массива состояний React? - PullRequest
0 голосов
/ 07 февраля 2020

Создание компонентов на основе массива состояний и предоставление каждому компоненту атрибута setState для массива приводит к странному поведению. Может быть из-за параллельных изменений состояния? Попробуйте фрагмент ниже. Нажатие на квадрат должно удалить его. Вместо этого всегда будет удален последний созданный квадрат.

Кажется, проблема в l ie в useEffect() в компоненте Square. Хотя во фрагменте это действительно бесполезно, я использую что-то подобное, чтобы сделать элементы перетаскиваемыми. Может кто-нибудь объяснить это поведение и предложить решение? Я чувствую, что пропустил что-то важное о Реакте здесь.

function Square(props){
  const [pos, setPos] = React.useState([props.top, props.left])

  React.useEffect(() => {
    props.setSquares(prevState => prevState.map((square, index) => index === props.index ? {...square, top: pos + 20} : square
    ))
  }, [pos])

  function deleteMe(){
    props.setSquares(prevState => prevState.filter((square, index) => index !== props.index))
  }

  return <div onClick={deleteMe} 
    style={{top: pos[0]+'px', left: pos[1]+'px'}}             
    className='square'>
  </div>
}

function App(){
  const [squares, setSquares] = React.useState([
    {top: 20, left: 0},{top: 20, left: 100},
    {top: 20, left: 200},{top: 20, left: 300}])

  React.useEffect(() => console.log('rerender', ...squares.map(s => s.left)))

  return <div>{squares.map((square, index) => <Square 
    setSquares={setSquares} index={index} 
    top={square.top} left={square.left}/>)}</div>
}
ReactDOM.render(<App/>, document.getElementById('root'))
.square{
  position: absolute;
  width: 80px;
  height: 80px;
  background: orange;
}
<div id='root'></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>

Редактировать: Как предложил Ник в комментариях, я изменил setSquare методы для использования prevState.map и filter, чтобы непреднамеренно не изменить предыдущее состояние.

Проблема остается, хотя ... всегда последний квадрат исчезает на экране. Использование эффекта при каждом повторном рендеринге показывает, что состояние обновляется правильно. Что мне здесь не хватает?

1 Ответ

1 голос
/ 07 февраля 2020

Вы должны помнить, что оператор распространения является мелкой копией, что означает, что newSquares[props.index].top = pos[0] + 20 в следующем коде изменяет объект в вашем массиве текущего состояния.

props.setSquares(prevState => {
  const newSquares = [...prevState]
  newSquares[props.index].top = pos[0] + 20 // this is mutating current state
  return newSquares
})

One Чтобы исправить это, нужно сделать следующее:

props.setSquares(prevState => {
  const newSquares = [...prevState]
  newSquares[props.index] = { 
    ...newSquares[props.index],
    top: pos[0] + 20
  };
  return newSquares
})

Другой вариант - просто отобразить существующее состояние и просто вернуть что-то новое для элемента, который вы хотите изменить:

props.setSquares(prevState => {
  return prevState.map((el, i) => {
    return i === props.index ? { ...el, top: pos[0] + 20 } : el;
  });
})
...