जब सेटटेरवल के भीतर प्रतिक्रिया स्थिति हुक का उपयोग कर अद्यतन नहीं हो रहा है


109

मैं नए रिएक्ट हुक की कोशिश कर रहा हूं और एक काउंटर के साथ क्लॉक कंपोनेंट है जिसे हर सेकंड बढ़ाना है। हालाँकि, मान एक से आगे नहीं बढ़ता है।

function Clock() {
  const [time, setTime] = React.useState(0);
  React.useEffect(() => {
    const timer = window.setInterval(() => {
      setTime(time + 1);
    }, 1000);
    return () => {
      window.clearInterval(timer);
    };
  }, []);

  return (
    <div>Seconds: {time}</div>
  );
}

ReactDOM.render(<Clock />, document.querySelector('#app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

<div id="app"></div>

जवाबों:


158

इसका कारण यह है कि कॉलबैक के setIntervalबंद होने के बाद केवल timeपहले रेंडर में ही वैरिएबल पहुंचता है , इसके timeबाद के रेंडर में नए मूल्य तक पहुंच नहीं है क्योंकि useEffect()दूसरी बार इनवॉइस नहीं किया गया है।

timesetIntervalकॉलबैक के भीतर हमेशा 0 का मान होता है ।

जैसे setStateआप परिचित हैं, स्टेट हुक के दो रूप हैं: एक जहां यह अद्यतन स्थिति में होता है, और कॉलबैक फॉर्म जिसे वर्तमान स्थिति में पास किया जाता है। आपको दूसरे फॉर्म का उपयोग करना चाहिए और setStateकॉलबैक के भीतर नवीनतम राज्य मान पढ़ना चाहिए। सुनिश्चित करने से पहले सुनिश्चित करें कि आपके पास नवीनतम राज्य मान है।

बोनस: वैकल्पिक दृष्टिकोण

दान अब्रामोव, setIntervalअपने ब्लॉग पोस्ट में हुक के साथ उपयोग करने के विषय में गहराई से जाता है और इस मुद्दे के आसपास वैकल्पिक तरीके प्रदान करता है। अत्यधिक इसे पढ़ने की सलाह देते हैं!

function Clock() {
  const [time, setTime] = React.useState(0);
  React.useEffect(() => {
    const timer = window.setInterval(() => {
      setTime(prevTime => prevTime + 1); // <-- Change this line!
    }, 1000);
    return () => {
      window.clearInterval(timer);
    };
  }, []);

  return (
    <div>Seconds: {time}</div>
  );
}

ReactDOM.render(<Clock />, document.querySelector('#app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

<div id="app"></div>


3
यह बहुत ही उपयोगी जानकारी है। शुक्रिया यंगशुन!
थोलल

8
आपका स्वागत है, मैंने कोड पर घूरते हुए केवल यह पता लगाने के लिए कि मैंने एक बहुत ही बुनियादी (लेकिन गैर-स्पष्ट) त्रुटि
अर्जित की थी

3
SO अपने सर्वश्रेष्ठ at
पैट्रिक हंड

42
Haha मैं अपनी पोस्ट प्लग करने के लिए यहाँ आया था और यह पहले से ही जवाब में है!
दान अब्रामोव

2
महान ब्लॉग पोस्ट। आँख खोलना।
बेंजामिनडक

19

useEffect खाली इनपुट सूची प्रदान करने पर घटक माउंट पर केवल एक बार फ़ंक्शन का मूल्यांकन किया जाता है।

हर बार राज्य के अद्यतन के setIntervalसाथ नया अंतराल सेट करने का एक विकल्प है setTimeout:

  const [time, setTime] = React.useState(0);
  React.useEffect(() => {
    const timer = setTimeout(() => {
      setTime(time + 1);
    }, 1000);
    return () => {
      clearTimeout(timer);
    };
  }, [time]);

का प्रदर्शन प्रभाव setTimeoutनगण्य है और आमतौर पर इसे नजरअंदाज किया जा सकता है। जब तक घटक उस बिंदु के लिए समय के प्रति संवेदनशील नहीं होता है जहां नए सेट टाइमआउट अवांछनीय प्रभाव पैदा करते हैं, दोनों setIntervalऔर setTimeoutदृष्टिकोण स्वीकार्य हैं।


12

जैसा कि अन्य ने बताया है, समस्या यह है कि useStateकेवल एक बार कहा जाता है (जैसा deps = []कि अंतराल को स्थापित करने के लिए:

React.useEffect(() => {
    const timer = window.setInterval(() => {
        setTime(time + 1);
    }, 1000);

    return () => window.clearInterval(timer);
}, []);

फिर, हर बार setIntervalटिक करने के बाद, यह वास्तव में कॉल करेगा setTime(time + 1), लेकिन कॉलबैक (बंद) परिभाषित होने पर timeहमेशा शुरू में इसका मूल्य होगा setInterval

आप के useStateसेटर के वैकल्पिक रूप का उपयोग कर सकते हैं और जो वास्तविक मूल्य आप सेट करना चाहते हैं (केवल उसी तरह setState) के बजाय कॉलबैक प्रदान कर सकते हैं :

setTime(prevTime => prevTime + 1);

लेकिन मैं आपको अपना स्वयं का useIntervalहुक बनाने के लिए प्रोत्साहित करूंगा ताकि आप DRY कर सकें और अपने कोड को setInterval घोषित रूप से उपयोग करके सरल कर सकें , जैसा कि Dan Abramov ने React Hooks के साथ SetInterval Declarative बनाने में यहां बताया है :

function useInterval(callback, delay) {
  const intervalRef = React.useRef();
  const callbackRef = React.useRef(callback);

  // Remember the latest callback:
  //
  // Without this, if you change the callback, when setInterval ticks again, it
  // will still call your old callback.
  //
  // If you add `callback` to useEffect's deps, it will work fine but the
  // interval will be reset.

  React.useEffect(() => {
    callbackRef.current = callback;
  }, [callback]);

  // Set up the interval:

  React.useEffect(() => {
    if (typeof delay === 'number') {
      intervalRef.current = window.setInterval(() => callbackRef.current(), delay);

      // Clear interval if the components is unmounted or the delay changes:
      return () => window.clearInterval(intervalRef.current);
    }
  }, [delay]);
  
  // Returns a ref to the interval ID in case you want to clear it manually:
  return intervalRef;
}


const Clock = () => {
  const [time, setTime] = React.useState(0);
  const [isPaused, setPaused] = React.useState(false);
        
  const intervalRef = useInterval(() => {
    if (time < 10) {
      setTime(time + 1);
    } else {
      window.clearInterval(intervalRef.current);
    }
  }, isPaused ? null : 1000);

  return (<React.Fragment>
    <button onClick={ () => setPaused(prevIsPaused => !prevIsPaused) } disabled={ time === 10 }>
        { isPaused ? 'RESUME ⏳' : 'PAUSE 🚧' }
    </button>

    <p>{ time.toString().padStart(2, '0') }/10 sec.</p>
    <p>setInterval { time === 10 ? 'stopped.' : 'running...' }</p>
  </React.Fragment>);
}

ReactDOM.render(<Clock />, document.querySelector('#app'));
body,
button {
  font-family: monospace;
}

body, p {
  margin: 0;
}

p + p {
  margin-top: 8px;
}

#app {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-height: 100vh;
}

button {
  margin: 32px 0;
  padding: 8px;
  border: 2px solid black;
  background: transparent;
  cursor: pointer;
  border-radius: 2px;
}
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

<div id="app"></div>

सरल और क्लीनर कोड का उत्पादन करने के अलावा, यह आपको स्वचालित रूप से अंतराल को रोकने (और स्पष्ट) की अनुमति delay = nullदेता है और अंतराल आईडी भी लौटाता है, यदि आप इसे स्वयं मैन्युअल रूप से रद्द करना चाहते हैं (जो दान के पदों में शामिल नहीं है)।

वास्तव में, इसमें सुधार भी किया जा सकता है, ताकि यह delayअप्रकाशित होने पर पुनः आरंभ न हो, लेकिन मुझे लगता है कि अधिकांश उपयोग के मामलों के लिए यह काफी अच्छा है।

यदि आप इसके setTimeoutबजाय एक समान उत्तर की तलाश कर रहे हैं setInterval, तो इसे देखें : https://stackoverflow.com/a/59274757/3723993

तुम भी की कथात्मक संस्करण पा सकते हैं setTimeoutऔर setInterval, useTimeoutऔर useInterval, के साथ साथ एक कस्टम useThrottledCallbackहुक में टाइपप्रति में लिखा https://gist.github.com/Danziger/336e75b6675223ad805a88c2dfdcfd4a


5

एक वैकल्पिक समाधान का उपयोग करना होगा useReducer, क्योंकि यह हमेशा वर्तमान स्थिति को पारित करेगा।

function Clock() {
  const [time, dispatch] = React.useReducer((state = 0, action) => {
    if (action.type === 'add') return state + 1
    return state
  });
  React.useEffect(() => {
    const timer = window.setInterval(() => {
      dispatch({ type: 'add' });
    }, 1000);
    return () => {
      window.clearInterval(timer);
    };
  }, []);

  return (
    <div>Seconds: {time}</div>
  );
}

ReactDOM.render(<Clock />, document.querySelector('#app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

<div id="app"></div>


useEffectयहां समय को अपडेट करने के लिए कई बार क्यों बुलाया जा रहा है, जबकि निर्भरता सरणी खाली है, जिसका अर्थ है कि useEffectकेवल पहली बार घटक / एप्लिकेशन रेंडरर्स को बुलाया जाना चाहिए?
ब्लैकमैथ

1
@BlackMath फ़ंक्शन को अंदर useEffectकेवल एक बार कहा जाता है, जब घटक पहली बार वास्तव में प्रस्तुत करता है। लेकिन इसके अंदर, setIntervalनियमित आधार पर समय बदलने के लिए एक प्रभारी है। मेरा सुझाव है कि आप इसके बारे में थोड़ा पढ़ लें setInterval, उसके बाद चीजें स्पष्ट होनी चाहिए! developer.mozilla.org/en-US/docs/Web/API/…
भालू-फुट

3

नीचे के रूप में यह ठीक काम करता है।

const [count , setCount] = useState(0);

async function increment(count,value) {
    await setCount(count => count + 1);
  }

//call increment function
increment(count);

count => count + 1कॉलबैक क्या, मेरे लिए काम किया धन्यवाद था!
निकोफिथेम

1

यह समाधान मेरे लिए काम नहीं करता है क्योंकि मुझे चर प्राप्त करने की आवश्यकता है और कुछ सामान को अपडेट न करें।

मुझे एक वादा के साथ हुक का अद्यतन मूल्य प्राप्त करने के लिए वर्कअराउंड मिलता है

उदाहरण के लिए:

async function getCurrentHookValue(setHookFunction) {
  return new Promise((resolve) => {
    setHookFunction(prev => {
      resolve(prev)
      return prev;
    })
  })
}

इससे मैं setInterval फ़ंक्शन के अंदर मान को इस तरह प्राप्त कर सकता हूं

let dateFrom = await getCurrentHackValue(setSelectedDateFrom);

0

समय बदलने पर पुन: रेंडर बताएं। बाहर निकलना

function Clock() {
  const [time, setTime] = React.useState(0);
  React.useEffect(() => {
    const timer = window.setInterval(() => {
      setTime(time + 1);
    }, 1000);
    return () => {
      window.clearInterval(timer);
    };
  }, [time]);

  return (
    <div>Seconds: {time}</div>
  );
}

ReactDOM.render(<Clock />, document.querySelector('#app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

<div id="app"></div>


2
इसके साथ समस्या यह है कि हर countबदलाव के बाद टाइमर क्लियर और रीसेट हो जाएगा ।
sumail

और ऐसा इसलिए setTimeout()है क्योंकि
एस्टुस
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.