React - setState () pada komponen yang tidak terpasang

92

Dalam komponen react saya, saya mencoba menerapkan pemintal sederhana saat permintaan ajax sedang berlangsung - saya menggunakan status untuk menyimpan status pemuatan.

Untuk beberapa alasan, bagian kode di bawah ini di komponen React saya membuat kesalahan ini

Hanya dapat memperbarui komponen yang dipasang atau dipasang. Ini biasanya berarti Anda memanggil setState () pada komponen yang tidak terpasang. Ini tidak boleh dilakukan. Silakan periksa kode untuk komponen yang tidak ditentukan.

Jika saya menyingkirkan panggilan setState pertama, kesalahan akan hilang.

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>
      );
  }

Pertanyaannya adalah mengapa saya mendapatkan kesalahan ini ketika komponen seharusnya sudah dipasang (seperti yang dipanggil dari componentDidMount) Saya pikir aman untuk mengatur status setelah komponen dipasang?

Marty
sumber
di konstruktor saya, saya menyetel "this.loadSearches = this.loadSearches.bind (this);" - sakit menambahkan itu ke pertanyaan
Marty
sudahkah Anda mencoba menyetel pemuatan ke nol di konstruktor Anda? Itu mungkin berhasil. this.state = { loading : null };
Pramesh Bajracharya

Jawaban:

69

Tanpa melihat fungsi render agak sulit. Meskipun sudah dapat melihat sesuatu yang harus Anda lakukan, setiap kali Anda menggunakan interval Anda harus menghapusnya saat melepas. Begitu:

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

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

Karena callback sukses dan error tersebut mungkin masih dipanggil setelah dilepas, Anda dapat menggunakan variabel interval untuk memeriksa apakah sudah terpasang.

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

Semoga ini bisa membantu, berikan fungsi render jika ini tidak berhasil.

Bersulang

Bruno Mota
sumber
2
Bruno, tidak bisakah Anda menguji keberadaan konteks "ini" .. ala ini && this.setState .....
james emanon
7
Atau sederhananya:componentWillUnmount() { clearInterval(this.loadInterval); }
Greg Herbowicz
@GregHerbowicz jika Anda melepas dan memasang komponen dengan pengatur waktu, komponen tersebut masih dapat diaktifkan meskipun Anda melakukan pembersihan sederhana.
corlaez
14

Pertanyaannya adalah mengapa saya mendapatkan kesalahan ini ketika komponen seharusnya sudah dipasang (seperti yang dipanggil dari componentDidMount) Saya pikir aman untuk mengatur status setelah komponen dipasang?

Itu tidak dipanggil dari componentDidMount. Anda componentDidMountmemunculkan fungsi panggilan balik yang akan dijalankan di tumpukan penangan pengatur waktu, bukan di tumpukan componentDidMount. Rupanya, pada saat callback Anda ( this.loadSearches) dijalankan, komponen telah dilepas.

Jadi jawaban yang diterima akan melindungi Anda. Jika Anda menggunakan beberapa asynchronous API lain yang tidak memungkinkan Anda untuk membatalkan fungsi asynchronous (sudah dikirimkan ke beberapa penangan), Anda dapat melakukan hal berikut:

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

Ini akan menghilangkan pesan kesalahan yang Anda laporkan dalam semua kasus meskipun rasanya seperti menyapu barang-barang di bawah permadani, terutama jika API Anda menyediakan kemampuan pembatalan (seperti setIntervalhalnya dengan clearInterval).

Marcus Junius Brutus
sumber
13
isMountedadalah antipattern yang disarankan facebook untuk tidak digunakan: facebook.github.io/react/blog/2015/12/16/…
Marty
1
Ya, saya mengatakan bahwa "rasanya seperti menyapu di bawah permadani".
Marcus Junius Brutus
5

Bagi siapa yang membutuhkan opsi lain, metode callback atribut ref bisa menjadi solusi. Parameter handleRef adalah referensi ke elemen DOM DOM.

Untuk informasi rinci tentang ref dan 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>
 )
}
burakhan alkan
sumber
5
Menggunakan ref untuk "isMounted" secara efektif sama persis dengan hanya menggunakan isMounted tetapi kurang jelas. isMounted bukanlah anti-pola karena namanya tetapi karena ini adalah anti-pola untuk menyimpan referensi ke komponen yang tidak dipasang.
Pajn
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() {
    ...
  }
}
john_per
sumber
Apakah ada cara untuk mencapai ini untuk komponen fungsional? @johnny
Tamjid
Untuk komponen fungsi saya akan menggunakan ref: const _isMounted = useRef (false); @Tamjid
john
1

Untuk anak cucu,

Kesalahan ini, dalam kasus kami, terkait dengan Reflux, callbacks, redirects, dan setState. Kami mengirim setState ke callback onDone, tetapi kami juga mengirim pengalihan ke callback onSuccess. Jika berhasil, callback onSuccess kami dijalankan sebelum onDone . Ini menyebabkan pengalihan sebelum percobaan setState . Jadi kesalahannya, setState pada komponen yang tidak terpasang.

Tindakan penyimpanan refluks:

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

Hubungi sebelum perbaikan:

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

Telepon setelah perbaikan:

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

Lebih

Dalam beberapa kasus, karena isMounted dari React adalah "deprecated / anti-pattern", kami telah mengadopsi penggunaan variabel _mounted dan memantaunya sendiri.

Geoffrey Hale
sumber
1

Bagikan solusi yang diaktifkan oleh kait reaksi .

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)
}, [])

solusi yang sama dapat diperpanjang kapan pun Anda ingin membatalkan permintaan sebelumnya pada perubahan id pengambilan, jika tidak, akan ada kondisi balapan di antara beberapa permintaan dalam penerbangan ( this.setStatedisebut rusak).

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

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

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

ini bekerja berkat penutupan javascript.

Secara umum, ide di atas mendekati pendekatan makeCancelable yang direkomendasikan oleh react doc, yang dengan jelas menyatakan

isMounted adalah Antipattern

Kredit

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

Xlee
sumber