componentDidMount chiamato PRIMA della richiamata ref


87

Problema

Sto impostando una reazione refutilizzando una definizione di funzione inline

render = () => {
    return (
        <div className="drawer" ref={drawer => this.drawerRef = drawer}>

quindi nel componentDidMountDOM il riferimento non è impostato

componentDidMount = () => {
    // this.drawerRef is not defined

La mia comprensione è che la refrichiamata dovrebbe essere eseguita durante il montaggio, tuttavia l'aggiunta di console.logistruzioni rivela componentDidMountviene chiamata prima della funzione di richiamata ref.

Altri esempi di codice che ho esaminato, ad esempio questa discussione su GitHub, indicano lo stesso presupposto, componentDidMountdovrebbe essere chiamato dopo qualsiasi refcallback definito in render, è persino dichiarato nella conversazione

Quindi componentDidMount viene attivato dopo che tutti i callback ref sono stati eseguiti?

Sì.

Sto usando React React 15.4.1

Qualcos'altro che ho provato

Per verificare che la reffunzione fosse chiamata, ho provato a definirla sulla classe come tale

setDrawerRef = (drawer) => {
  this.drawerRef = drawer;
}

poi in render

<div className="drawer" ref={this.setDrawerRef}>

La registrazione della console in questo caso rivela che la richiamata viene effettivamente chiamata dopo componentDidMount


6
Potrei sbagliarmi, ma quando usi la funzione freccia per il metodo di rendering, acquisirà il valore di thisdall'ambito lessicale al di fuori della tua classe. Prova a sbarazzarti della sintassi della funzione freccia per i metodi della tua classe e vedi se aiuta.
Yoshi

3
@ GProst Questa è la natura della mia domanda. Ho inserito console.log in entrambe le funzioni e componentDidMount è in esecuzione per primo, il secondo callback ref.
quickshiftin

3
Ho appena avuto un problema simile: per noi, fondamentalmente, ci siamo persi all'inizio rendere quindi dovevamo sfruttare componentDidUpdate, in quanto componentDidMountnon fa parte del ciclo di vita dell'aggiornamento . Probabilmente non è il tuo problema, ma ho pensato che potrebbe valere la pena sollevarlo come potenziale soluzione.
Alexander Nied

4
Lo stesso con React 16. La documentazione afferma chiaramente ref callbacks are invoked before componentDidMount or componentDidUpdate lifecycle hooks.ma questo non sembra essere vero :(
Ryan H.

1
1. La dichiarazione della freccia di riferimento è: ref = {ref => { this.drawerRef = ref }}2. i riferimenti pari vengono invocati prima di componentDidMount; ref è accessibile solo dopo il rendering iniziale quando viene eseguito il rendering del div nel tuo caso. Quindi, devi essere in grado di accedere al ref nel livello successivo, cioè in componentWillReceiveProps usando this.drawerRef3. Se provi ad accedere prima del montaggio iniziale, otterrai solo valori non definiti di ref.
bh4r4th

Risposte:


156

Risposta breve:

React garantisce che refs siano impostati prima componentDidMounto componentDidUpdatehooks. Ma solo per i bambini che sono stati effettivamente resi .

componentDidMount() {
  // can use any refs here
}

componentDidUpdate() {
  // can use any refs here
}

render() {
  // as long as those refs were rendered!
  return <div ref={/* ... */} />;
}

Nota questo non significa che "React imposta sempre tutti i ref prima che questi hook vengano eseguiti".
Diamo un'occhiata ad alcuni esempi in cui gli arbitri non vengono impostati.


I riferimenti non vengono impostati per gli elementi che non sono stati renderizzati

React chiamerà solo i callback ref per gli elementi effettivamente restituiti dal rendering .

Ciò significa che se il tuo codice ha l'aspetto

render() {
  if (this.state.isLoading) {
    return <h1>Loading</h1>;
  }

  return <div ref={this._setRef} />;
}

e inizialmente this.state.isLoadingè true, si dovrebbe non aspettare this._setRefdi essere chiamato prima componentDidMount.

Questo dovrebbe avere senso: se il tuo primo rendering è tornato <h1>Loading</h1>, non c'è modo per React di sapere che in qualche altra condizione restituisce qualcos'altro che necessita di un ref da allegare. Non c'è inoltre nulla su cui impostare il riferimento: l' <div>elemento non è stato creato perché il filerender() metodo diceva che non doveva essere renderizzato.

Quindi, con questo esempio, sparerà solo componentDidMount. Tuttavia, quando this.state.loadingcambia infalse , vedrai this._setRefprima gli allegati e poi si componentDidUpdateattiverà.


Fai attenzione agli altri componenti

Nota che se passi figli con ref ad altri componenti, c'è la possibilità che stiano facendo qualcosa che impedisce il rendering (e causa il problema).

Ad esempio, questo:

<MyPanel>
  <div ref={this.setRef} />
</MyPanel>

non funzionerebbe se MyPanelnon includesse props.childrennel suo output:

function MyPanel(props) {
  // ignore props.children
  return <h1>Oops, no refs for you today!</h1>;
}

Anche in questo caso, non è un bug: non ci sarebbe nulla per React su cui impostare ref perché l'elemento DOM non è stato creato .


I riferimenti non vengono impostati prima del ciclo di vita se vengono passati a un file nidificato ReactDOM.render()

Analogamente alla sezione precedente, se passi un bambino con un riferimento a un altro componente, è possibile che questo componente faccia qualcosa che impedisce di attaccare il riferimento in tempo.

Ad esempio, forse non sta restituendo il bambino da render()e invece sta chiamando ReactDOM.render()un hook del ciclo di vita. Puoi trovare un esempio di questo qui . In questo esempio, rendiamo:

<MyModal>
  <div ref={this.setRef} />
</MyModal>

Ma MyModalesegue una ReactDOM.render()chiamata nel suo componentDidUpdate metodo del ciclo di vita:

componentDidUpdate() {
  ReactDOM.render(this.props.children, this.targetEl);
}

render() {
  return null;
}

A partire da React 16, tali chiamate di rendering di primo livello durante un ciclo di vita verranno ritardate finché i cicli di vita non saranno stati eseguiti per l'intero albero . Questo spiegherebbe perché non vedi gli arbitri allegati in tempo.

La soluzione a questo problema è utilizzare i portali invece delle ReactDOM.renderchiamate annidate :

render() {
  return ReactDOM.createPortal(this.props.children, this.targetEl);
}

In questo modo il nostro <div>con un riferimento è effettivamente incluso nell'output di rendering.

Quindi, se si verifica questo problema, è necessario verificare che non ci sia nulla tra il componente e il ref che potrebbe ritardare il rendering dei bambini.

Non utilizzare setStateper memorizzare ref

Assicurati di non utilizzare setStateper memorizzare il ref in ref callback, poiché è asincrono e prima che sia "finito", componentDidMountverrà eseguito per primo.


Ancora un problema?

Se nessuno dei suggerimenti precedenti aiuta, segnala un problema in React e daremo un'occhiata.


2
Ho apportato una modifica alla mia risposta per spiegare anche questa situazione. Vedere la prima sezione. Spero che sia di aiuto!
Dan Abramov

Ciao @DanAbramov, grazie per questo! Sfortunatamente non sono riuscito a sviluppare un caso riproducibile quando l'ho incontrato per la prima volta. Purtroppo non lavoro più a quel progetto e da allora non sono più stato in grado di riprodurlo. La domanda è diventata abbastanza popolare, però, che sono d'accordo, cercare di trovare il caso riproducibile è la chiave poiché molte persone sembrano aver riscontrato il problema.
quickshiftin

Penso che in molti casi ciò sia stato probabilmente dovuto a un malinteso. In React 15 ciò potrebbe accadere anche a causa di un errore che è stato inghiottito (React 16 ha una migliore gestione degli errori e lo impedisce). Sono felice di rivedere più casi quando ciò accade, quindi sentiti libero di aggiungerli nei commenti.
Dan Abramov

Aiuta! Non ho davvero notato che ci fosse un preloader.
Nazariy

1
Questa risposta mi ha davvero aiutato. Stavo lottando con qualche riferimento vuoto "refs", e beh, si è scoperto che gli "elementi" non erano affatto renderizzati.
MarkSkayff

1

Un'osservazione diversa del problema.

Mi sono reso conto che il problema si è verificato solo durante la modalità di sviluppo. Dopo ulteriori indagini, ho scoperto che la disabilitazione react-hot-loadernella configurazione del mio Webpack previene questo problema.

sto usando

  • "react-hot-loader": "3.1.3"
  • "webpack": "4.10.2",

Ed è un'app elettronica.

La mia configurazione di sviluppo Webpack parziale

const webpack = require('webpack')
const merge = require('webpack-merge')
const baseConfig = require('./webpack.config.base')

module.exports = merge(baseConfig, {

  entry: [
    // REMOVED THIS -> 'react-hot-loader/patch',
    `webpack-hot-middleware/client?path=http://localhost:${port}/__webpack_hmr`,
    '@babel/polyfill',
    './app/index'
  ],
  ...
})

È diventato sospetto quando ho visto che l'uso della funzione inline in render () funzionava, ma l'uso di un metodo vincolato si è bloccato.

Funziona comunque

class MyComponent {
  render () {
    return (
      <input ref={(el) => {this.inputField = el}}/>
    )
  }
}

Blocco con il caricatore a caldo (ref non è definito in componentDidMount)

class MyComponent {
  constructor (props) {
    super(props)
    this.inputRef = this.inputRef.bind(this)
  }

  inputRef (input) {
    this.inputField = input
  }

  render () {
    return (
      <input ref={this.inputRef}/>
    )
  }
}

Ad essere onesti, la ricarica a caldo è stata spesso problematica da ottenere "corretta". Con gli strumenti di sviluppo che si aggiornano rapidamente, ogni progetto ha una configurazione diversa. Forse la mia configurazione particolare potrebbe essere corretta. Ti farò sapere qui se è così.


Questo potrebbe spiegare perché ho problemi con questo in CodePen, ma l'utilizzo di una funzione inline non ha aiutato nel mio caso.
robartsd

0

Il problema può sorgere anche quando si tenta di utilizzare un riferimento di un componente non montato come utilizzare un riferimento in setinterval e non si cancella l'intervallo impostato durante lo smontaggio del componente.

componentDidMount(){
    interval_holder = setInterval(() => {
    this.myref = "something";//accessing ref of a component
    }, 2000);
  }

intervallo sempre chiaro come ad esempio,

componentWillUnmount(){
    clearInterval(interval_holder)
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.