web-dev-qa-db-fra.com

composantDidMount appelé AVANT le rappel de référence

Problème

Je suis en train de réagir ref en utilisant une définition de fonction inline

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

alors dans componentDidMount la référence DOM n'est pas définie

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

D'après ce que je comprends, le callback ref devrait être exécuté pendant le montage, mais l'ajout d'instructions console.log révèle que componentDidMount est appelé before la fonction de rappel ref.

D'autres exemples de code que j'ai consultés, par exemple cette discussion sur github indiquent la même hypothèse, componentDidMount devrait être appelé après tout rappel ref défini dans render, c'est même indiqué dans la conversation

Alors composantDidMount est déclenché après que tous les rappels de référence aient été exécuté?

Oui.

J'utilise react 15.4.1

Quelque chose d'autre que j'ai essayé

Pour vérifier que la fonction ref était appelée, j'ai essayé de la définir dans la classe en tant que telle

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

puis dans render

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

Dans ce cas, la consignation de la console révèle que le rappel est effectivement appelé aftercomponentDidMount

50
quickshiftin

Réponse courte:

React garantit que les références sont définies avant les hooks componentDidMount ou componentDidUpdate. Mais uniquement pour les enfants qui ont effectivement été rendus.

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

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

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

Notez que cela ne signifie pas “React définit toujours tout ref avant que ces hooks ne soient exécutés”.
Voyons quelques exemples où les références ne pas sont définies.


Les références ne sont pas définies pour les éléments qui n'ont pas été rendus.

React appellera uniquement les rappels de référence pour les éléments que vous avez renvoyés de à partir de render.

Cela signifie que si votre code ressemble à

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

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

et initialement this.state.isLoading est true, vous devriez ne pas vous attendre à ce que this._setRef soit appelé avant componentDidMount.

Cela devrait avoir un sens: si votre premier rendu renvoie <h1>Loading</h1>, React ne peut pas savoir que, sous une autre condition, il renvoie quelque chose qui nécessite l’ajout d’une référence. Il n’existe également que rien pour définir la référence sur: l’élément <div> n’a pas été créé, car la méthode render() a déclaré qu’il ne devait pas être restitué.

Donc, avec cet exemple, seule componentDidMount sera déclenchée. Cependant, lorsque this.state.loading devient false, vous verrez d'abord this._setRef attaché, puis componentDidUpdate sera déclenché.


Attention aux autres composants

Notez que si vous transmettez les enfants avec des références à d’autres composants, il est possible qu’ils fassent quelque chose qui empêche le rendu (et cause le problème).

Par exemple, ceci:

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

ne fonctionnerait pas si MyPanel n'incluait pas props.children dans sa sortie:

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

Encore une fois, ce n’est pas un bug: il n’y aurait rien pour que React mette la référence à parce que l’élément DOM n’a pas été créé.


Les références ne sont pas définies avant les cycles de vie si elles sont passées à une ReactDOM.render() imbriquée.

Comme dans la section précédente, si vous transmettez un enfant avec une référence à un autre composant, il est possible que ce composant fasse quelque chose qui empêche l’attachement de la référence dans le temps.

Par exemple, il se peut que l’enfant ne soit pas renvoyé de render() et qu’il appelle plutôt ReactDOM.render() dans un hook de cycle de vie. Vous pouvez trouver un exemple de ceci ici . Dans cet exemple, nous rendons:

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

Mais MyModal effectue un appel ReactDOM.render() dans la méthode itscomponentDidUpdate:

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

render() {
  return null;
}

Depuis React 16, ces appels de rendu de niveau supérieur au cours d'un cycle de vie seront retardés jusqu'à ce que les cycles de vie se soient écoulés pour tout l'arbre. Cela expliquerait pourquoi vous ne voyez pas les références attachées à temps.

La solution à ce problème consiste à utiliser portals au lieu d'appels ReactDOM.render imbriqués:

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

De cette façon, notre <div> avec une référence est réellement inclus dans le rendu.

Par conséquent, si vous rencontrez ce problème, vous devez vérifier qu’il n’ya rien entre votre composant et la référence qui puisse retarder le rendu des enfants.

N'utilisez pas setState pour stocker des références

Assurez-vous de ne pas utiliser setState pour stocker le ref ref dans le rappel, car il est asynchrone et avant qu'il soit "fini", componentDidMount sera exécuté en premier.


Toujours un problème?

Si aucun des conseils ci-dessus ne vous aide, déposez un problème dans React et nous examinerons. 

86
Dan Abramov

Une observation différente du problème.

J'ai réalisé que le problème ne se produisait qu'en mode de développement . Après une enquête plus approfondie, j'ai constaté que la désactivation de react-hot-loader dans ma configuration Webpack évite ce problème.

J'utilise

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

Et c'est une application électronique.

Ma configuration partielle de développement 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'
  ],
  ...
})

Cela est devenu suspect quand j'ai vu que l'utilisation de la fonction inline dans render () fonctionnait, mais que l'utilisation d'une méthode liée plantait.

Fonctionne dans tous les cas

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

Crash avec react-hot-loader (la référence n'est pas définie dans composantDidMount)

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

  inputRef (input) {
    this.inputField = input
  }

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

Pour être honnête, le rechargement à chaud a souvent été problématique pour obtenir "le droit". Avec la mise à jour rapide des outils de développement, chaque projet a une configuration différente ..__ Peut-être que ma configuration particulière pourrait être corrigée. Je vous ferai savoir ici si c'est le cas.

0
Kev