93 votes

componentDidMount appelé AVANT le callback ref

Problème

J'établis une réaction ref en utilisant une définition de fonction en ligne

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 j'ai compris, le ref doit être exécuté pendant le montage, mais l'ajout de la commande console.log les déclarations révèlent componentDidMount s'appelle antes de la fonction de rappel de la référence.

D'autres exemples de code que j'ai examinés, par exemple cette discussion sur github indiquent la même hypothèse, componentDidMount devrait s'appeler après tout ref les callbacks définis dans render c'est même a déclaré dans la conversation

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

Oui.

J'utilise react 15.4.1

Autre chose que j'ai essayé

Pour vérifier le ref était appelée, j'ai essayé de la définir dans la classe comme suit

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

alors dans render

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

L'enregistrement de la console dans ce cas révèle que le callback est effectivement appelé. après componentDidMount

6 votes

Je me trompe peut-être, mais lorsque vous utilisez la fonction flèche pour les méthodes de rendu, elle capturera la valeur de this à partir du champ lexical en dehors de votre classe. Essayez de vous débarrasser de la syntaxe de la fonction flèche pour les méthodes de votre classe et voyez si cela vous aide.

0 votes

Cela fonctionne bien pour moi. Êtes-vous sûr que les appels de rappel de refs après DidMount ? Faites 2 console.logs dans les deux fonctions et voyez laquelle était la première

0 votes

J'utilise la version 15.5.4 de React.

168voto

Dan Points 16670

Réponse courte :

React garantit que les références sont définies avant componentDidMount o componentDidUpdate crochets. Mais seulement pour les enfants qui a été rendu .

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 que "React fixe toujours tous les arbitres avant que ces crochets ne fonctionnent".
Regardons quelques exemples où les arbitres Ne le fais pas. se mettre en place.


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

React n'appellera les callbacks ref que pour les éléments que vous avez réellement retourné par le rendu .

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 es true vous devriez no s'attendre à this._setRef à appeler avant componentDidMount .

Cela devrait avoir un sens : si votre premier rendu a retourné <h1>Loading</h1> il n'y a aucun moyen pour React de savoir que dans une autre condition, il retourne quelque chose d'autre qui nécessite une référence pour être attaché. Il y a aussi rien pour mettre la référence : le site <div> n'a pas été créé parce que l'élément render() La méthode a dit que ça ne devrait pas être rendu.

Donc, dans cet exemple, seulement componentDidMount se déclenchera. Cependant, quand this.state.loading les changements apportés à false vous verrez this._setRef attaché d'abord, et ensuite componentDidUpdate fera feu.


Attention aux autres composants

Notez que si vous passez des enfants avec des références vers le bas à d'autres composants il y a une chance qu'ils fassent quelque chose qui empêche le rendu (et qui cause le problème).

Par exemple, ceci :

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

ne fonctionnerait pas si MyPanel ne comprenait 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 React pour définir la référence parce que l'élément DOM n'a pas été créé. .


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

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 de joindre la référence à temps.

Par exemple, il se peut qu'il ne renvoie pas l'enfant à partir de render() et appelle plutôt ReactDOM.render() dans un crochet de cycle de vie. Vous pouvez trouver un exemple de ceci aquí . Dans cet exemple, nous rendons :

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

Mais MyModal effectue un ReactDOM.render() appeler son componentDidUpdate la méthode du cycle de vie :

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

render() {
  return null;
}

Depuis React 16, de tels les appels de rendu de niveau supérieur pendant un cycle de vie seront retardés jusqu'à ce que les cycles de vie aient été exécutés pour l'ensemble de l'arbre. . Cela expliquerait pourquoi vous ne voyez pas les refs attachés à temps.

La solution à ce problème est d'utiliser portails au lieu d'être imbriqués ReactDOM.render appels :

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

De cette façon, notre <div> avec une référence est réellement incluse dans la sortie du rendu.

Donc, si vous rencontrez ce problème, vous devez vérifier qu'il n'y a rien entre votre composant et la référence qui pourrait retarder le rendu des enfants.

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

Assurez-vous que vous n'utilisez pas setState pour stocker la ref dans la callback ref, car elle est asynchrone et avant qu'elle ne soit "terminée", 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 y jetterons un œil.

2 votes

J'ai modifié ma réponse pour expliquer cette situation également. Voir la première section. J'espère que cela vous aidera !

0 votes

Salut @DanAbramov merci pour cela ! Malheureusement, je n'ai pas été en mesure de développer un cas reproductible lorsque je l'ai rencontré pour la première fois. Malheureusement, je ne travaille plus sur ce projet et je n'ai pas été en mesure de le reproduire depuis. La question est devenue suffisamment populaire pour que je sois d'accord pour essayer de trouver un cas reproductible, car de nombreuses personnes semblent rencontrer ce problème.

0 votes

Je pense qu'il est probable que dans de nombreux cas, cela était dû à un malentendu. Dans React 15, cela pouvait également se produire en raison d'une erreur qui a été avalée (React 16 a une meilleure gestion des erreurs et empêche cela). Je suis heureux d'examiner d'autres cas où cela se produit, alors n'hésitez pas à les ajouter dans les commentaires.

1voto

Amida Points 517

Une observation différente du problème.

Je me suis rendu compte que le problème ne se produisait qu'en mode développement. Après plus d'investigations, j'ai découvert que la désactivation de react-hot-loader dans ma configuration de Webpack empêche 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'
  ],
  ...
})

J'ai eu des doutes quand j'ai vu que l'utilisation de la fonction inline dans render () fonctionnait, mais que l'utilisation d'une méthode bound plantait.

Fonctionne dans tous les cas

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

Crash avec react-hot-loader (ref est indéfini dans componentDidMount)

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é difficile à mettre en place. Avec des outils de développement qui se mettent à jour rapidement, chaque projet a une configuration différente. Peut-être que ma configuration particulière pourrait être corrigée. Je vous le ferai savoir ici si c'est le cas.

0 votes

Cela pourrait expliquer pourquoi j'ai des problèmes avec CodePen, mais l'utilisation d'une fonction en ligne n'a pas aidé dans mon cas.

0voto

random coder Points 1

Le problème peut également survenir lorsque vous essayez d'utiliser une référence d'un composant démonté, comme l'utilisation d'une référence dans setinterval et que vous n'effacez pas set interval pendant le démontage du composant.

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

toujours un intervalle clair comme par exemple,

componentWillUnmount(){
    clearInterval(interval_holder)
}

-1voto

Dans componentDidMount, pouvez-vous trouver votre élément ref (div.drawer) dans le DOM du navigateur ? Si non, vous ne pouvez pas avoir sa référence. Puisque le problème se trouve dans un autre code plus important, la raison pourrait être que l'élément ref (div.drawer) n'est pas rendu.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X