React - takılmamış bileşen üzerinde setState ()


92

React bileşenimde, bir ajax isteği devam ederken basit bir döndürücü uygulamaya çalışıyorum - yükleme durumunu saklamak için durumu kullanıyorum.

Bazı nedenlerden dolayı, React bileşenimdeki bu kod parçası bu hatayı atıyor

Yalnızca monte edilmiş veya monte edilmiş bir bileşeni güncelleyebilir. Bu genellikle, bağlanmamış bir bileşende setState () çağırdığınız anlamına gelir. Bu işlem yok. Lütfen tanımlanmamış bileşenin kodunu kontrol edin.

İlk setState çağrısından kurtulursam, hata kaybolur.

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

Soru, bileşenin zaten bağlanması gerektiğinde bu hatayı neden alıyorum (componentDidMount'tan çağrıldığı gibi), bileşen monte edildikten sonra durumu ayarlamanın güvenli olduğunu düşündüm?


kurucumda "this.loadSearches = this.loadSearches.bind (this);" ayarlıyorum - bunu soruya ekleyeceğim
Marty

kurucunuzda yüklemeyi null olarak ayarlamayı denediniz mi? İşe yarayabilir. this.state = { loading : null };
Pramesh Bajracharya

Yanıtlar:


69

Render işlevini görmeden biraz zor. Yapmanız gereken bir şeyi halihazırda fark edebilse de, bir aralığı her kullandığınızda, ayırdığınızda onu temizlemeniz gerekir. Yani:

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

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

Bu başarı ve hata geri aramaları, bağlantı kesildikten sonra da çağrılabileceğinden, bağlı olup olmadığını kontrol etmek için aralık değişkenini kullanabilirsiniz.

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

Umarım bu, işi yapmazsa render işlevini sağlar.

Şerefe


2
Bruno, "bu" bağlamın varlığını test edemez misin .. ala this && this.setState .....
james emanon

7
Ya da basitçe:componentWillUnmount() { clearInterval(this.loadInterval); }
Greg Herbowicz

@GregHerbowicz Bileşeni zamanlayıcı ile söküp takıyorsanız, basit temizleme yapsanız bile yine de ateşlenebilir.
corlaez

14

Soru, bileşenin zaten bağlanması gerektiğinde bu hatayı neden alıyorum (componentDidMount'tan çağrıldığı gibi), bileşen monte edildikten sonra durumu ayarlamanın güvenli olduğunu düşündüm?

O edilir değil aradı componentDidMount. Sizin componentDidMount, yığınında değil, zamanlayıcı işleyicisinin yığınında çalıştırılacak bir geri arama işlevi oluşturur componentDidMount. Görünüşe göre, callback ( this.loadSearches) işleminiz yürütüldüğünde, bileşen bağlantısı kesilmiş.

Yani kabul edilen cevap sizi koruyacaktır. Eşzamansız işlevleri iptal etmenize izin vermeyen başka bir eşzamansız API kullanıyorsanız (zaten bir işleyiciye gönderilmiştir) aşağıdakileri yapabilirsiniz:

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

Bu, her durumda rapor ettiğiniz hata mesajından kurtulacaktır, ancak özellikle API'niz bir iptal özelliği sağlıyorsa (olduğu setIntervalgibi clearInterval) , halının altına süpürmek gibi geliyor .


13
isMountedfacebook'un kullanmamayı önerdiği bir anti-modeldir: facebook.github.io/react/blog/2015/12/16/…
Marty

1
Evet, "halının altına süpürmek gibi geliyor" diyorum.
Marcus Junius Brutus

5

Kimler için başka bir seçeneğe ihtiyaç duyarsa, ref özniteliğinin geri arama yöntemi geçici bir çözüm olabilir. HandleRef parametresi, div DOM öğesine yapılan referanstır.

Refs ve DOM hakkında ayrıntılı bilgi için: 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
Etkin bir şekilde "isMounted" için bir ref kullanmak, sadece isMounted kullanmakla tamamen aynı şeydir, ancak daha az anlaşılırdır. isMounted, adı nedeniyle bir anti-model değildir, ancak bağlanmamış bir bileşene referansları tutmak için bir anti-model olduğu için.
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() {
    ...
  }
}

İşlevsel bir bileşen için bunu başarmanın bir yolu var mı? @john_per
Tamjid

Bir işlev bileşeni için ref: const _isMounted = useRef (false); @Tamjid
john_per

1

Gelecek nesil için,

Bizim durumumuzda bu hata Reflux, geri çağırmalar, yönlendirmeler ve setState ile ilgiliydi. Bir onDone geri aramasına setState gönderdik, ancak onSuccess geri aramasına da bir yönlendirme gönderdik. Başarı durumunda, onSuccess geri aramamız onDone'dan önce yürütülür . Bu , setState denenmeden önce yeniden yönlendirmeye neden olur . Böylece, bağlanmamış bir bileşendeki setState hatası.

Reflü deposu eylemi:

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

Düzeltmeden önce arayın:

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

Düzeltildikten sonra ara:

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

Daha

Bazı durumlarda, React'in isMounted "kullanımdan kaldırıldı / anti-pattern" olduğundan, _mounted bir değişken kullanımını benimsedik ve onu kendimiz izledik.


1

React kancalarının etkinleştirdiği bir çözümü paylaşın .

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

Aynı çözüm, getirme kimliği değişikliklerinde önceki istekleri iptal etmek istediğinizde genişletilebilir , aksi takdirde birden çok uçuş içi istek arasında yarış koşulları oluşur (sıra dışı this.setStateçağrılır).

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

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

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

bu , javascript'teki kapanışlar sayesinde çalışır .

Genel olarak, yukarıdaki fikir , tepki belgesinin önerdiği makeCancelable yaklaşımına yakındı.

isMounted bir Antipattern'dir

Kredi

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

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.