componentDidMount chamado BEFORE ref callback

86

Problema

Estou definindo uma reação refusando uma definição de função inline

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

então, no componentDidMountDOM, a referência não está definida

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

Meu entendimento é que o refretorno de chamada deve ser executado durante a montagem, no entanto, adicionar console.loginstruções revela componentDidMounté chamado antes da função de retorno de chamada ref.

Outros exemplos de código que examinei, por exemplo, esta discussão no github indicam a mesma suposição, componentDidMountdevem ser chamados após qualquer refretorno de chamada definido em render, é até mesmo declarado na conversa

Então, componentDidMount é disparado após todos os callbacks ref terem sido executados?

Sim.

Estou usando o react 15.4.1

Outra coisa que tentei

Para verificar se a reffunção estava sendo chamada, tentei defini-la na classe como tal

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

então em render

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

O registro do console, neste caso, revela que o retorno de chamada está realmente sendo chamado após componentDidMount

troca rápida
fonte
6
Posso estar errado, mas quando você está usando a função de seta para o método de renderização, ela capturará o valor de thisdo escopo léxico fora de sua classe. Tente se livrar da sintaxe da função de seta para seus métodos de classe e veja se isso ajuda.
Yoshi
3
@GProst Essa é a natureza da minha pergunta. Eu coloquei console.log em ambas as funções e componentDidMount está sendo executado primeiro, o retorno de chamada ref em segundo.
quickshift
3
Acabamos de ter um problema semelhante - para nós, basicamente, não o percebemos no início rendere, portanto, precisávamos aproveitar componentDidUpdate, já que componentDidMountnão faz parte do ciclo de vida de atualização . Provavelmente não é seu problema, mas pensei que valeria a pena levantar como uma solução potencial.
Alexander Nied
4
O mesmo com React 16. A documentação afirma claramente, ref callbacks are invoked before componentDidMount or componentDidUpdate lifecycle hooks.mas isso não parece ser verdade :(
Ryan H.
1
1. a declaração de seta ref é: ref = {ref => { this.drawerRef = ref }}2. até mesmo refs são invocados antes de componentDidMount; ref só pode ser acessado após a renderização inicial quando o div no seu caso é renderizado. Portanto, você deve ser capaz de acessar o ref no próximo nível, ou seja, em componentWillReceiveProps usando this.drawerRef3. Se você tentar acessar antes da montagem inicial, obterá apenas os valores indefinidos de ref.
bh4r4 de

Respostas:

153

Resposta curta:

React garante que refs são colocados antes componentDidMountou componentDidUpdateganchos. Mas apenas para crianças que realmente foram renderizadas .

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

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

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

Observe que isso não significa “React sempre define todas as referências antes que esses ganchos sejam executados”.
Vejamos alguns exemplos em que os refs não são definidos.


Refs não são definidos para elementos que não foram renderizados

O React só chamará retornos de chamada ref para os elementos que você realmente retornou da renderização .

Isso significa que se o seu código for semelhante

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

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

e inicialmente this.state.isLoadingé true, você não deve esperar this._setRefser chamado antes componentDidMount.

Isso deve fazer sentido: se sua primeira renderização retornou <h1>Loading</h1>, não há como o React saber que sob alguma outra condição ele retorna algo que precisa de um ref para ser anexado. Também não há nada para definir o ref: o <div>elemento não foi criado porque o render()método disse que ele não deveria ser renderizado.

Portanto, com este exemplo, apenas componentDidMountdisparará. No entanto, quando this.state.loadingmudar parafalse , você verá o this._setRefanexo primeiro e, em seguida, componentDidUpdateirá disparar.


Cuidado com outros componentes

Observe que se você passar filhos com refs para outros componentes, há uma chance de que eles estejam fazendo algo que impede a renderização (e causa o problema).

Por exemplo, este:

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

não funcionaria se MyPanelnão fosse incluído props.childrenem sua saída:

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

Novamente, não é um bug: não haveria nada para o React definir o ref porque o elemento DOM não foi criado .


Refs não são definidos antes dos ciclos de vida se forem passados ​​para um aninhado ReactDOM.render()

Semelhante à seção anterior, se você passar um filho com um ref para outro componente, é possível que esse componente faça algo que evite anexar o ref a tempo.

Por exemplo, talvez ele não esteja retornando a criança de render()e, em vez disso, esteja chamando ReactDOM.render()um gancho de ciclo de vida. Você pode encontrar um exemplo disso aqui . Nesse exemplo, renderizamos:

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

Mas MyModalrealiza uma ReactDOM.render()chamada em seu componentDidUpdate método de ciclo de vida:

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

render() {
  return null;
}

Desde o React 16, essas chamadas de renderização de nível superior durante um ciclo de vida serão atrasadas até que os ciclos de vida sejam executados para toda a árvore . Isso explicaria por que você não está vendo os árbitros anexados a tempo.

A solução para esse problema é usar portais em vez de ReactDOM.renderchamadas aninhadas :

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

Desta forma, nosso <div>com um ref é realmente incluído na saída do render.

Portanto, se você encontrar esse problema, precisa verificar se não há nada entre seu componente e o ref que possa atrasar a renderização dos filhos.

Não use setStatepara armazenar refs

Certifique-se de que você não está usando setStatepara armazenar o ref no retorno de chamada ref, pois ele é assíncrono e antes de "terminar", componentDidMountserá executado primeiro.


Ainda é um problema?

Se nenhuma das dicas acima ajudar, registre um problema no React e nós daremos uma olhada.

Dan Abramov
fonte
2
Eu fiz uma edição em minha resposta para explicar essa situação também. Veja a primeira seção. Espero que isto ajude!
Dan Abramov
Olá @DanAbramov obrigado por isso! Infelizmente, não fui capaz de desenvolver um caso reproduzível quando o encontrei pela primeira vez. Infelizmente, não trabalho mais nesse projeto e não consigo reproduzi-lo desde então. A questão se tornou popular o suficiente, porém, que eu concordo, tentar encontrar o caso reproduzível é a chave, pois muitas pessoas parecem estar enfrentando o problema.
quickshift em
Acho que em muitos casos isso se deveu a um mal-entendido. No React 15, isso também pode acontecer devido a um erro que foi engolido (o React 16 tem melhor tratamento de erros e evita isso). Terei prazer em revisar mais casos quando isso acontecer, então fique à vontade para adicioná-los nos comentários.
Dan Abramov
Ajuda! Eu realmente não percebi que havia um pré-carregador.
Nazariy
1
Essa resposta realmente me ajudou. Eu estava lutando com algumas referências vazias de "refs" e, bem, descobri que os "elementos" não estavam sendo renderizados.
MarkSkayff
1

Uma observação diferente do problema.

Percebi que o problema só ocorreu durante o modo de desenvolvimento. Após mais investigação, descobri que desativar a react-hot-loaderconfiguração do meu Webpack evita esse problema.

estou usando

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

E é um aplicativo de elétrons.

Minha configuração de desenvolvimento parcial do 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'
  ],
  ...
})

Fiquei desconfiado quando vi que usar a função embutida em render () estava funcionando, mas usar um método vinculado estava travando.

Funciona em qualquer caso

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

Bater com react-hot-loader (ref é indefinida em 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, o hot reload costuma ser problemático para "acertar". Com as ferramentas de desenvolvimento atualizadas rapidamente, cada projeto tem uma configuração diferente. Talvez minha configuração particular pudesse ser corrigida. Avisarei você aqui se for o caso.

Kev
fonte
Isso pode explicar por que estou tendo problemas com isso no CodePen, mas usar uma função embutida não ajudou no meu caso.
robartsd
0

O problema também pode surgir quando você tenta usar um ref de um componente não montado, como usar um ref em setinterval e não limpa o intervalo definido durante a desmontagem do componente.

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

intervalo sempre claro, como por exemplo,

componentWillUnmount(){
    clearInterval(interval_holder)
}
codificador aleatório
fonte