अभिकर्मक - setState () अनमाउंट घटक पर


92

मेरी प्रतिक्रिया घटक में im एक साधारण स्पिनर को लागू करने की कोशिश कर रहा है, जबकि एक अजाक्स अनुरोध प्रगति पर है - लोडिंग स्थिति को संग्रहीत करने के लिए राज्य का उपयोग करके im।

किसी कारण से मेरे रिएक्ट घटक में नीचे दिए गए कोड का यह टुकड़ा इस त्रुटि को फेंकता है

केवल एक घुड़सवार या बढ़ते घटक को अपडेट कर सकते हैं। इसका आम तौर पर मतलब है कि आपने अनमाउंट किए गए घटक पर सेटस्टेट () कहा है। यह एक नो-ऑप है। कृपया अपरिभाषित घटक के लिए कोड की जाँच करें।

अगर मैं पहले सेटस्टैट कॉल से छुटकारा पा लेता हूं तो त्रुटि दूर हो जाती है।

constructor(props) {
  super(props);
  this.loadSearches = this.loadSearches.bind(this);

  this.state = {
    loading: false
  }
}

loadSearches() {

  this.setState({
    loading: true,
    searches: []
  });

  console.log('Loading Searches..');

  $.ajax({
    url: this.props.source + '?projectId=' + this.props.projectId,
    dataType: 'json',
    crossDomain: true,
    success: function(data) {
      this.setState({
        loading: false
      });
    }.bind(this),
    error: function(xhr, status, err) {
      console.error(this.props.url, status, err.toString());
      this.setState({
        loading: false
      });
    }.bind(this)
  });
}

componentDidMount() {
  setInterval(this.loadSearches, this.props.pollInterval);
}

render() {

    let searches = this.state.searches || [];


    return (<div>
          <Table striped bordered condensed hover>
          <thead>
            <tr>
              <th>Name</th>
              <th>Submit Date</th>
              <th>Dataset &amp; Datatype</th>
              <th>Results</th>
              <th>Last Downloaded</th>
            </tr>
          </thead>
          {
          searches.map(function(search) {

                let createdDate = moment(search.createdDate, 'X').format("YYYY-MM-DD");
                let downloadedDate = moment(search.downloadedDate, 'X').format("YYYY-MM-DD");
                let records = 0;
                let status = search.status ? search.status.toLowerCase() : ''

                return (
                <tbody key={search.id}>
                  <tr>
                    <td>{search.name}</td>
                    <td>{createdDate}</td>
                    <td>{search.dataset}</td>
                    <td>{records}</td>
                    <td>{downloadedDate}</td>
                  </tr>
                </tbody>
              );
          }
          </Table >
          </div>
      );
  }

सवाल यह है कि जब घटक को पहले ही माउंट किया जाना चाहिए (जैसा कि घटकडिमाउंट से कहा जा रहा है) तो मुझे यह त्रुटि क्यों हो रही है? मुझे लगा कि घटक के माउंट होने के बाद राज्य को सेट करना सुरक्षित था?


मेरे निर्माता में मैं "this.loadSearches = this.loadSearches.bind (यह)" सेट कर रहा हूं; - बीमार इस सवाल से जोड़ें
मार्टी

क्या आपने अपने निर्माता में नल लोड करने की कोशिश की है ? वह काम कर सकता है। this.state = { loading : null };
प्रमेश बजराचार्य

जवाबों:


69

रेंडर फ़ंक्शन देखे बिना थोड़ा कठिन है। हालाँकि आपको पहले से ही कुछ करना चाहिए, हर बार जब आप एक अंतराल का उपयोग करते हैं, तो आपको इसे स्पष्ट करने के लिए मिला। इसलिए:

componentDidMount() {
    this.loadInterval = setInterval(this.loadSearches, this.props.pollInterval);
}

componentWillUnmount () {
    this.loadInterval && clearInterval(this.loadInterval);
    this.loadInterval = false;
}

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

this.loadInterval && this.setState({
    loading: false
});

आशा है कि यह मदद करता है, अगर यह काम नहीं करता है तो रेंडर फ़ंक्शन प्रदान करें।

चियर्स


2
ब्रूनो, क्या आप केवल "इस" संदर्भ के अस्तित्व के लिए परीक्षण नहीं कर सकते थे .. यह इस && this.setState .....
james emanon

6
या बस:componentWillUnmount() { clearInterval(this.loadInterval); }
ग्रेग हर्बिकेज़

@GregHerbowicz यदि आप टाइमर के साथ घटक को अनमाउंट और माउंट कर रहे हैं तो इसे अभी भी निकाल दिया जा सकता है, भले ही आप सरल समाशोधन करें।
corlaez

14

सवाल यह है कि जब घटक को पहले ही माउंट किया जाना चाहिए (जैसा कि घटकडिमाउंट से कहा जा रहा है) तो मुझे यह त्रुटि क्यों हो रही है? मुझे लगा कि घटक के माउंट होने के बाद राज्य को सेट करना सुरक्षित था?

इसे कहा नहीं जाता है componentDidMount। आपका componentDidMountकॉलबैक फ़ंक्शन करता है जिसे टाइमर हैंडलर के स्टैक में निष्पादित किया जाएगा, स्टैक के स्टैक में नहीं componentDidMount। जाहिरा तौर पर, जब तक आपके कॉलबैक ( this.loadSearches) को निष्पादित किया जाता है तब तक घटक अनमाउंट हो जाता है।

तो स्वीकृत उत्तर आपकी रक्षा करेगा। यदि आप कुछ अन्य अतुल्यकालिक एपीआई का उपयोग कर रहे हैं जो आपको अतुल्यकालिक कार्यों को रद्द करने की अनुमति नहीं देता है (पहले से ही कुछ हैंडलर को प्रस्तुत किया गया है) तो आप निम्न कार्य कर सकते हैं:

if (this.isMounted())
     this.setState(...

यह आपको सभी मामलों में रिपोर्ट किए गए त्रुटि संदेश से छुटकारा दिलाएगा, हालांकि यह गलीचा के नीचे स्वीपिंग सामान की तरह महसूस करता है, खासकर यदि आपका एपीआई एक रद्द क्षमता प्रदान करता है (जैसा कि setIntervalकरता है clearInterval)।


12
isMountedएक एंटीपैटर है जिसे facebook उपयोग न करने की सलाह देता है: facebook.github.io/react/blog/2015/12/12/16/…
Marty

1
हां, मैं कहता हूं कि "यह गलीचा के नीचे सामान की तरह लग रहा है"।
मार्कस जूनियस ब्रूटस

5

जिनके लिए एक और विकल्प की जरूरत है, रेफरी की कॉलबैक विधि एक वर्कअराउंड हो सकती है। हैंडल रीफ का पैरामीटर DOM तत्व को div करने के लिए संदर्भ है।

Refs और DOM के बारे में विस्तृत जानकारी के लिए: https://facebook.github.io/react/docs/refs-and-the-dom.html

handleRef = (divElement) => {
 if(divElement){
  //set state here
 }
}

render(){
 return (
  <div ref={this.handleRef}>
  </div>
 )
}

5
प्रभावी रूप से "एमाउंटेड" के लिए रेफरी का उपयोग करना बिल्कुल वैसा ही है जैसा कि केवल इस्माउंटेड लेकिन कम स्पष्ट का उपयोग करना है। isMounted अपने नाम के कारण एक विरोधी प्रतिमान नहीं है, लेकिन क्योंकि यह एक असम्बद्ध घटक के संदर्भ को धारण करने के लिए एक प्रतिमान है।
पजन

3
class myClass extends Component {
  _isMounted = false;

  constructor(props) {
    super(props);

    this.state = {
      data: [],
    };
  }

  componentDidMount() {
    this._isMounted = true;
    this._getData();
  }

  componentWillUnmount() {
    this._isMounted = false;
  }

  _getData() {
    axios.get('https://example.com')
      .then(data => {
        if (this._isMounted) {
          this.setState({ data })
        }
      });
  }


  render() {
    ...
  }
}

क्या एक कार्यात्मक घटक के लिए इसे प्राप्त करने का एक तरीका है? @ जों_पर
तमजीद

एक फंक्शन कंपोनेंट के लिए मैं Ref का उपयोग करता हूँ: const _isMounted = useRef (false); @ तमजीद
john_per

1

पश्चाताप के लिए,

हमारे मामले में यह त्रुटि, रिफ्लक्स, कॉलबैक, रीडायरेक्ट और सेटस्टेट से संबंधित थी। हमने onDone कॉलबैक पर एक सेटस्टैट भेजा था, लेकिन हमने ऑन कॉल असफल कॉलबैक पर एक रीडायरेक्ट भी भेजा। सफलता के मामले में, ऑनडोन से पहले हमारी ऑनस्क्यू कॉलबैक निष्पादित होती है । यह सेट किए गए प्रयास से पहले पुनर्निर्देशित करता है । इस प्रकार त्रुटि, अनमाउंट घटक पर सेट करें।

भाटा की दुकान कार्रवाई:

generateWorkflow: function(
    workflowTemplate,
    trackingNumber,
    done,
    onSuccess,
    onFail)
{...

फिक्स से पहले कॉल करें:

Actions.generateWorkflow(
    values.workflowTemplate,
    values.number,
    this.setLoading.bind(this, false),
    this.successRedirect
);

फिक्स के बाद कॉल करें:

Actions.generateWorkflow(
    values.workflowTemplate,
    values.number,
    null,
    this.successRedirect,
    this.setLoading.bind(this, false)
);

अधिक

कुछ मामलों में, चूंकि रिएक्ट का आईमाउंट "डिप्रेस्ड / एंटी-पैटर्न" है, इसलिए हमने _mounted वैरिएबल का उपयोग किया है और इसे स्वयं मॉनिटर कर रहा है।


1

प्रतिक्रिया हुक द्वारा सक्षम समाधान साझा करें ।

React.useEffect(() => {
  let isSubscribed = true

  callApi(...)
    .catch(err => isSubscribed ? this.setState(...) : Promise.reject({ isSubscribed, ...err }))
    .then(res => isSubscribed ? this.setState(...) : Promise.reject({ isSubscribed }))
    .catch(({ isSubscribed, ...err }) => console.error('request cancelled:', !isSubscribed))

  return () => (isSubscribed = false)
}, [])

एक ही समाधान को तब तक बढ़ाया जा सकता है, जब आप फ़िच आईडी में बदलाव के पिछले अनुरोधों को रद्द करना चाहते हैं, अन्यथा कई इन-फ्लाइट अनुरोधों ( this.setStateऑर्डर से बाहर) के बीच दौड़ की स्थिति होगी ।

React.useEffect(() => {
  let isCancelled = false

  callApi(id).then(...).catch(...) // similar to above

  return () => (isCancelled = true)
}, [id])

यह जावास्क्रिप्ट में बंद करने के लिए धन्यवाद काम करता है ।

सामान्य तौर पर, ऊपर दिया गया विचार अभिकर्मक डॉक द्वारा अनुशंसित मेकेंसेबल दृष्टिकोण के करीब था , जो स्पष्ट रूप से बताता है

isMounted एक Antipattern है

श्रेय

https://juliangaramendy.dev/use-promise-subscription/

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