componentDidMount llamado ANTES de devolución de llamada de referencia

86

Problema

Estoy configurando una reacción refusando una definición de función en línea

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

entonces en componentDidMountla referencia DOM no se establece

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

Tengo entendido que la refdevolución de llamada debe ejecutarse durante el montaje, sin embargo, agregar console.logdeclaraciones revela componentDidMountse llama antes de la función de devolución de llamada ref.

Otras muestras de código que he visto, por ejemplo, esta discusión en github indican la misma suposición, se componentDidMountdebe llamar después de cualquier refdevolución de llamada definida en render, incluso se indica en la conversación

Entonces, ¿componentDidMount se dispara después de que se hayan ejecutado todas las devoluciones de llamada de referencia?

Si.

Estoy usando react 15.4.1

Algo más que he probado

Para verificar refque se estaba llamando a la función, intenté definirla en la clase como tal

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

luego en render

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

El registro de la consola en este caso revela que la devolución de llamada se está llamando después componentDidMount

quickshiftin
fuente
6
Puede que me equivoque, pero cuando usa la función de flecha para los métodos de renderizado, capturará el valor del thisámbito léxico fuera de su clase. Intente deshacerse de la sintaxis de la función de flecha para sus métodos de clase y vea si ayuda.
Yoshi
3
@GProst Esa es la naturaleza de mi pregunta. Puse console.log en ambas funciones y componentDidMount se está ejecutando primero, la devolución de llamada ref en segundo lugar.
quickshiftin
3
Acabo de tener un problema similar; para nosotros, básicamente, lo perdimos al principio rendery, por lo tanto, necesitábamos aprovecharlo componentDidUpdate, ya componentDidMountque no es parte del ciclo de vida de actualización . Probablemente no sea su problema, pero pensé que valdría la pena plantearlo como una posible solución.
Alexander Nied
4
Lo mismo con React 16. La documentación dice claramente, ref callbacks are invoked before componentDidMount or componentDidUpdate lifecycle hooks.pero esto no parece ser cierto :(
Ryan H.
1
1. La declaración de la flecha de referencia es: ref = {ref => { this.drawerRef = ref }}2. Incluso las referencias se invocan antes de componentDidMount; Solo se puede acceder a ref después del renderizado inicial cuando se renderiza el div en su caso. Por lo tanto, debe poder acceder a la referencia en el siguiente nivel, es decir, en componentWillReceiveProps usando this.drawerRef3. Si intenta acceder antes del montaje inicial, obtendrá solo los valores indefinidos de ref.
bh4r4th

Respuestas:

153

Respuesta corta:

React garantiza que las referencias se establezcan antes componentDidMounto componentDidUpdateenganches. Pero solo para niños que realmente se renderizaron .

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

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

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

Tenga en cuenta que esto no significa que "React siempre establece todas las referencias antes de que se ejecuten estos ganchos".
Veamos algunos ejemplos en los que los árbitros no se establecen.


Las referencias no se establecen para elementos que no se renderizaron

React solo llamará a las devoluciones de llamada de referencia para los elementos que realmente devolvió del renderizado .

Esto significa que si su código se parece a

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

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

y en un principio this.state.isLoadinges true, usted debe no esperar this._setRefa ser llamado antes componentDidMount.

Esto debería tener sentido: si su primer render regresó <h1>Loading</h1>, no hay forma posible de que React sepa que bajo alguna otra condición devuelve algo más que necesita una referencia adjunta. Tampoco hay nada para establecer la referencia: el <div>elemento no fue creado porque el render()método dijo que no debería ser renderizado.

Entonces, con este ejemplo, solo componentDidMountse disparará. Sin embargo, cuando this.state.loadingcambie afalse , verá this._setRefadjunto primero y luego componentDidUpdatedisparará.


Cuidado con otros componentes

Tenga en cuenta que si pasa a los niños con referencias a otros componentes, existe la posibilidad de que estén haciendo algo que evite la representación (y cause el problema).

Por ejemplo, esto:

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

no funcionaría si MyPanelno incluyera props.childrenen su salida:

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

Nuevamente, no es un error: no habría nada para que React establezca la referencia porque el elemento DOM no fue creado .


Las referencias no se establecen antes de los ciclos de vida si se pasan a un anidado ReactDOM.render()

Al igual que en la sección anterior, si pasa un hijo con una referencia a otro componente, es posible que este componente haga algo que impida adjuntar la referencia a tiempo.

Por ejemplo, tal vez no está devolviendo al hijo de render(), sino que está llamando ReactDOM.render()a un enlace de ciclo de vida. Puedes encontrar un ejemplo de esto aquí . En ese ejemplo, renderizamos:

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

Pero MyModalrealiza una ReactDOM.render()llamada en su componentDidUpdate método de ciclo de vida:

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

render() {
  return null;
}

Desde React 16, estas llamadas de procesamiento de nivel superior durante un ciclo de vida se retrasarán hasta que los ciclos de vida se hayan ejecutado para todo el árbol . Esto explicaría por qué no ve los árbitros adjuntos a tiempo.

La solución a este problema es utilizar portales en lugar de ReactDOM.renderllamadas anidadas :

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

De esta manera, nuestro <div>con una referencia se incluye realmente en la salida de render.

Entonces, si encuentra este problema, debe verificar que no haya nada entre su componente y la referencia que pueda retrasar la representación de los niños.

No lo use setStatepara almacenar referencias

Asegúrese de que no está usando setStatepara almacenar la referencia en la devolución de llamada de referencia, ya que es asincrónica y antes de que "termine", componentDidMountse ejecutará primero.


¿Sigue siendo un problema?

Si ninguno de los consejos anteriores le ayuda, presente un problema en React y le echaremos un vistazo.

Dan Abramov
fuente
2
También hice una edición en mi respuesta para explicar esta situación. Vea la primera sección. ¡Espero que esto ayude!
Dan Abramov
Hola @DanAbramov, ¡gracias por esto! Desafortunadamente, no pude desarrollar un caso reproducible cuando lo encontré por primera vez. Lamentablemente ya no trabajo en ese proyecto y no he podido reproducir desde entonces. Sin embargo, la pregunta se ha vuelto lo suficientemente popular, estoy de acuerdo, tratar de encontrar el caso reproducible es clave ya que muchas personas parecen estar experimentando el problema.
quickshiftin
Creo que es probable que en muchos casos esto se deba a un malentendido. En React 15, esto también podría suceder debido a un error que se tragó (React 16 tiene un mejor manejo de errores y evita esto). Me complace revisar más casos cuando esto sucede, así que siéntase libre de agregarlos en los comentarios.
Dan Abramov
Ayuda! Realmente no me di cuenta de que había un precargador.
Nazariy
1
Esta respuesta realmente me ayudó. Estaba luchando con una referencia vacía de "refs", y bueno, resultó que los "elementos" no se estaban renderizando en absoluto.
MarkSkayff
1

Una observación diferente del problema.

Me di cuenta de que el problema solo ocurría en el modo de desarrollo. Después de más investigación, descubrí que la desactivación react-hot-loaderen mi configuración de Webpack previene este problema.

estoy usando

  • "react-hot-loader": "3.1.3"
  • "paquete web": "4.10.2",

Y es una aplicación electrónica.

Mi configuración de desarrollo parcial de Webpack

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

Se volvió sospechoso cuando vi que el uso de la función en línea en render () estaba funcionando, pero el uso de un método enlazado fallaba.

Funciona en cualquier caso

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

Crash con react-hot-loader (la referencia no está definida en componentDidMount)

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

  inputRef (input) {
    this.inputField = input
  }

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

Para ser honesto, la recarga en caliente a menudo ha sido problemática para hacerlo "bien". Con las herramientas de desarrollo que se actualizan rápidamente, cada proyecto tiene una configuración diferente. Quizás mi configuración particular podría arreglarse. Te lo haré saber aquí si ese es el caso.

Kev
fuente
Esto podría explicar por qué tengo problemas con esto en CodePen, pero el uso de una función en línea no ha ayudado en mi caso.
robartsd
0

El problema también puede surgir cuando intentas usar una referencia de un componente desmontado, como usar una referencia en setinterval y no borras el intervalo establecido durante el desmontaje del componente.

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

intervalo siempre claro como por ejemplo,

componentWillUnmount(){
    clearInterval(interval_holder)
}
codificador aleatorio
fuente