Как вызвать setInterval при нажатии кнопки в приложении React?

Я пытаюсь написать программу, которая увеличивает счетчик, когда пользователь нажимает «Пуск», и прекращает увеличивать его, когда пользователь нажимает «Стоп».

Вывод должен выглядеть так

и тогда это должно быть

ХХХ-0-ХХХ

ХХХ-1-ХХХ

ХХХ-2-ХХХ ....

процесс должен остановиться при нажатии кнопки «Стоп».

Код (который не работает):

import React from 'react'

import './App.css'

function App() {
  const [counter, setCounter] = React.useState(0)
  const [intervalID, setIntervalID] = React.useState(null)

  const increment = () => {
    const nextCounter = counter + 1
    setCounter(nextCounter)
  }

  const onClickStart = () => {
    setIntervalID(setInterval(increment, 100))
  }

  const onClickStop = () => {
    clearInterval(intervalID)
    setIntervalID(null)
  }

  return (
    <div className = "App">
      <label>XXX-{counter}-XXX</label>
      <button onClick = {onClickStart}>Start</button>
      <button onClick = {onClickStop}>Stop</button>
    </div>
  )
}

export default App

дело в том, что после этого

ХХХ-1-ХХХ

показывается, а потом все останавливается. increment вызывается, но счетчик не увеличивается.

Я просмотрел похожие вопросы, но не нашел ни одного, где setInterval вызывается нажатием кнопки. Похоже, я не понимаю чего-то очевидного. Поэтому любая помощь приветствуется.

ОБНОВЛЯТЬ:

Здесь нет ответа на мой вопрос:

Функция JavaScript On-click для запуска и остановки интервалов

это аналогичный вопрос, но он не отвечает на мой вопрос.

Ответ на мой вопрос написан Деннисом Катсом, и ответ заключается в том, чтобы исправить функцию приращения:

    const increment = () => setCounter(oldCounter => oldCounter + 1);
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
0
0
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проблема в том, что ваша функция приращения создает замыкание над переменной counter, а setInterval выполняет начальную increment функцию, где counter всегда равно 0, каждый раз. В результате setInterval постоянно устанавливает счетчик на 1. Подробнее о замыканиях в React можно прочитать здесь, если вы с ними не знакомы.

Тем временем, чтобы исправить ваш код, setCounter может дополнительно использовать функцию, которая сопоставляет текущее значение (даже после обновления состояния) с тем, каким должно быть следующее значение. Таким образом, increment на самом деле не закрывается по состоянию счетчика, а получает его динамически через setCounter, и поэтому он может правильно увеличивать значение.

Вот рабочий пример:

function App() {
  const [counter, setCounter] = React.useState(0)
  const [intervalID, setIntervalID] = React.useState(null)

  // here is the primary change!!
  // we simply tell setCounter to add 1 to the old counter value
  const increment = () => setCounter(oldCounter => oldCounter + 1);

  const onClickStart = () => {
    if (intervalID === null) // small fix to prevent starting multiple intervals
      setIntervalID(setInterval(increment, 100));
  }

  const onClickStop = () => {
    clearInterval(intervalID);
    setIntervalID(null);
  }

  return ( 
    <div className = "App">
      <label>XXX-{counter}-XXX</label>
      <button onClick = {onClickStart}>Start</button>
      <button onClick = {onClickStop}>Stop</button>
    </div>
  )
}

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

<div id = "root"></div>

Спасибо - работает, и я понял в чем проблема, и почти понял как это лечится. Единственное, чего я не понимаю, так это "const increment = () => setCounter(oldCounter => oldCounter + 1);" Как это работает? increment — это функция без параметра, которая вызывает функцию setCounter(oldCounter). Но откуда взялся oldCounter?

John Smith 09.01.2023 04:58

Теперь понимаю. Видимо, я должен был сначала прочитать этот medium.com/ableneo/…. Особенно «тип SetStateAction похож на перегруженный тип initialProps. Пользователь функции может установить состояние напрямую или использовать функцию с предыдущим состоянием в качестве аргумента». Так что спасибо, теперь все ясно

John Smith 09.01.2023 10:54

Другие вопросы по теме