Реагировать на добавление и удаление компонента при нажатии кнопки

Я новичок в React. Здесь я пытаюсь добавить / удалить компонент «Таймер» нажатием кнопки +/-. Он отлично работает при нажатии «+», но не «-». Я считал, что каждый раз, когда я устанавливаю состояние, компонент перерисовывается. Но здесь он не отображается при нажатии кнопки «-».

  1. Что здесь может быть не так?
  2. Также приветствуется любой лучший подход к тому же.
function App() {
  var [timers, setTimers] = useState([]);
  var [count, setCount] = useState(0);

  const addTimer = () => {
    setCount(count + 1);
    timers.push(count);
    setTimers(timers);
    console.log(timers); //[0]
  };

  const removeTimer = () => {
    timers.pop();
    setTimers(timers);
    console.log(timers); //[]
  };

  return (
    <div>
      <button className="button" onClick={addTimer}>
        +
      </button>

      <button className="button" onClick={removeTimer}>
        -
      </button>

      <div>
        {timers.map((t) => (
          <Timer key={t} />
        ))}
      </div>
    </div>
  );
}

person Deva44    schedule 30.01.2021    source источник
comment
Попробуйте использовать setTimers ([... таймеры]), чтобы обнаружить изменения массива.   -  person lissettdm    schedule 30.01.2021


Ответы (4)


Проблема в том, что React повторяет рендеринг только при изменении состояния. И он проверяет этот факт, выполняя поверхностное сравнение. Итак, для объектов (или массивов, как в вашем случае), даже если вы изменяете содержимое объекта, объект ссылка остается прежним. Потому что timers.pop() не создает новый массив. Он изменяет текущий массив.

И React сделает что-то вроде этого: old array === new array ? и вернется как true, потому что это та же ссылка на массив / объект.

PS: Ваша кнопка (+) работает только потому, что вы тоже делаете setCount(count+1). Кстати, вы должны изменить это на setCount((prevState) => prevState + 1);

Когда ваше новое состояние зависит от вашего последнего состояния, я советую вам всегда использовать эту форму вызова setState:

setState((prevState) => {
  // READ AND USE OLD prevState
  // RETURN BRAND NEW newState
});

Вы можете сделать что-то вроде:

const addTimer = () => {
  setCount((prevState) => prevState + 1);
  setTimers((prevState) => {
    const newTimers = Array.from(prevState);  // CREATING A NEW ARRAY OBJECT
    timers.push(count);
    return newTimers;  
  });
};

const removeTimer = () => {
  setTimers((prevState) => {
    const newTimers = Array.from(prevState);  // CREATING A NEW ARRAY OBJECT
    newTimers.pop();
    return newTimers;  
  });
};
person cbdeveloper    schedule 30.01.2021

Вы изменяете состояние, которое не запускает рендеринг, см. Возможность не изменять данные в документации React.

Переписывание на неизменную логику может быть:

const addTimer = () => {
  setCount((prevCount ) => prevCount + 1);
  setTimers((prevTimers) => [...prevTimers, count]);
};

const removeTimer = () => {
  setTimers((prevTimers) => prevTimers.slice(0, prevTimers.length - 1));
};
person Dennis Vash    schedule 30.01.2021

Потому что вы пытаетесь изменить состояние. Здесь (в React) есть принцип не изменять состояние. Таким образом, вы не должны выталкивать, а делать что-то подобное в функции удаления. Это поможет избежать побочных эффектов:

setTimers(state => state.filter((timer, index) => index !== state.length - 1))

протестировано с помощью тестовой среды React

person Elisey    schedule 30.01.2021

Возможно, причина того, что это не работает, заключается в том, что вы неправильно добавляете / удаляете таймеры, используя методы push / pop.

Если вы хотите, чтобы ваши компоненты перерисовывались, вам нужно изменить состояние с помощью разрушения.

Возможно, это не лучшее решение, но должно работать:

const addTimer = () => {
    setCount(count + 1)
    setTimers([...timers, count])
}

const removeTimer = () => {
    let filteredTimers = timers.filter((timer, index) => index !== (timers.length-1))
    setTimers([...filteredTimers])
}
person Jisatsu    schedule 30.01.2021