Home > other >  I want to subtract the value of 'percent' from the function by 0.25 in React
I want to subtract the value of 'percent' from the function by 0.25 in React

Time:03-16

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;

CodePudding user response:

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

CodePudding user response:

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>

CodePudding user response:

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;

CodePudding user response:

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),
          console.log('percent: ', percent)
        );
        /*if (percent > 0){ 
          setPercent(c => c - 0.25);
          console.log("percent = ", percent);
        }
        else {
          setPercent(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)

CodePudding user response:

<button onClick={()=>{start()}}>{"Start"}</button>

should be

<button onClick={()=>{start();}}>{"Start"}</button>

Also try to add setPercent to the dep of useCallback

  • Related