1

I want to subtract the value of 'percent' from the function by 0.25.

However, subtraction does not work.

I used setState, but I don't know why it doesn't work.

import React, {useState, useRef, useCallback} from 'react';

const Ques = () => {
const [percent,setPercent] = useState(1);
const intervalRef = useRef(null);

const start = useCallback(() =>{
    if (intervalRef.current !== null){
      return;
    }
    intervalRef.current = setInterval(()=>{
      if (percent > 0){ 
        setPercent(c => c - 0.25);
        console.log("percent = ", percent);
  
      }
      else {
        setPercent(c => 1);
      }
    }, 1000);

  }, []);

  return (
      <div>
          <button onClick={()=>{start()}}>{"Start"}</button>
      </div>
  );
}
export default Ques;
Junha_Park
  • 51
  • 4
  • Just wanted to share an observation: `setPercent(c => 1);` may be simplified as `setPercent(1);`. Also, just above the ` – jsN00b Mar 16 '22 at 01:53
  • Subtraction is operated on 'button', but 'percent=1' is continuously output on the console. And setPercent(1); does not work. – Junha_Park Mar 16 '22 at 02:01

4 Answers4

3

Issue

The enqueued state updates are working correctly but you've a stale enclosure over the percent state in the interval callback that you are logging, it never will update.

Solution

If you want to log the percent state then use an useEffect hook to log changes.

const Ques = () => {
  const [percent, setPercent] = useState(1);
  const intervalRef = useRef(null);

  useEffect(() => {
    console.log("percent = ", percent); // <-- log state changes here
  }, [percent]);

  const start = useCallback(() => {
    if (intervalRef.current !== null) {
      return;
    }
    intervalRef.current = setInterval(() => {
      setPercent((c) => Math.max(0, c - 0.25)); // <-- simpler updater function
    }, 1000);
  }, []);

  return (
    <div>
      Percent: {percent * 100}
      <button onClick={start}>Start</button>
    </div>
  );
};

Edit i-want-to-subtract-the-value-of-percent-from-the-function-by-0-25-in-react

Drew Reese
  • 165,259
  • 14
  • 153
  • 181
2

You can create a ref for percent also and chenge its current value as:

codesandbox link

import React, { useRef, useCallback } from "react";

const Ques = () => {
  const percentRef = useRef(1);
  const intervalRef = useRef(null);

  const start = useCallback(() => {
    if (intervalRef.current !== null) {
      return;
    }
    intervalRef.current = setInterval(() => {
      console.log("percent = ", percentRef.current);
      percentRef.current > 0
        ? (percentRef.current -= 0.25)
        : (percentRef.current = 1);
    }, 1000);
  }, []);

  return (
    <div>
      <button onClick={start}>Start</button>
    </div>
  );
};
export default Ques;
DecPK
  • 24,537
  • 6
  • 26
  • 42
1

I think useCallback and useRef is not a good fit. Below is a minimal verifiable example using useState and useEffect. Note this function appropriately performs cleanup on the timer when the component is unmounted. Click Run to run the code snippet and click start to begin running the effect.

function App() {
  const [percent, setPercent] = React.useState(1)
  const [running, setRunning] = React.useState(false)
  
  React.useEffect(() => {
    if (!running) return
    const t = window.setTimeout(() => {
      setPercent(c => c > 0 ? c - 0.25 : 1)
    }, 1000)
    return () => window.clearTimeout(t)
  }, [running, percent])
  
  return <div>
    <button onClick={() => setRunning(true)} children="start" />
    <pre>percent: {percent}</pre>
  </div>
}

ReactDOM.render(<App/>, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
よつば
  • 467
  • 1
  • 8
1

This may be one possible solution to achieve what is presumed to be the desired objective:

Code Snippet

const {useState, useRef, useCallback} = React;

const Ques = () => {
  const [percent,setPercent] = useState(1);
  const intervalRef = useRef(null);

  const start = useCallback((flag) => {
      if (intervalRef.current !== null){
        if (flag && flag === 'end') clearInterval(intervalRef.current);
        return;
      }
      intervalRef.current = setInterval(() => {
        setPercent(
          prev => (prev > 0 ? prev - 0.25 : 1)
        );
      }, 1000);

    }, []);

    return (
        <div>
          percent: {percent} <br/> <br/>
          <button onClick={() => start('bgn')}>Start</button> &emsp;
          <button onClick={() => start('end')}>Stop</button>
        </div>
    );
}

ReactDOM.render(
  <div>
    <h3>DEMO</h3>
    <Ques />
  </div>,
  document.getElementById('rd')
);
<div id='rd' />
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>

Explanation

  • There are two buttons Start and Stop
  • Both invoke the same start method, but with different params (flag)
  • If intervalRef is already set (ie, not null) and flag is end, clear the interval
  • The percent is added to the UI to see real-time changes to its value
  • setPercent is modified to use prev (which holds the correct state)
jsN00b
  • 3,584
  • 2
  • 8
  • 21