Como forçar a remontagem nos componentes React?

103

Digamos que eu tenha um componente de visualização com renderização condicional:

render(){
    if (this.state.employed) {
        return (
            <div>
                <MyInput ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div>
                <MyInput ref="unemployment-reason" name="unemployment-reason" />
                <MyInput ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}

MyInput é parecido com isto:

class MyInput extends React.Component {

    ...

    render(){
        return (
            <div>
                <input name={this.props.name} 
                    ref="input" 
                    type="text" 
                    value={this.props.value || null}
                    onBlur={this.handleBlur.bind(this)}
                    onChange={this.handleTyping.bind(this)} />
            </div>
        );
    }
}

Vamos dizer que employedé verdade. Sempre que eu mudo para false e a outra visualização é renderizada, apenas unemployment-durationé reinicializado. Também unemployment-reasoné preenchido com o valor de job-title(se um valor foi fornecido antes da alteração da condição).

Se eu alterar a marcação na segunda rotina de renderização para algo assim:

render(){
    if (this.state.employed) {
        return (
            <div>
                <MyInput ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div>
                <span>Diff me!</span>
                <MyInput ref="unemployment-reason" name="unemployment-reason" />
                <MyInput ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}

Parece que tudo funciona bem. Parece que o React simplesmente falha em diferenciar 'cargo' e 'motivo de desemprego'.

Por favor me diga o que estou fazendo de errado ...

o01
fonte
1
O que há data-reactidem cada um dos elementos MyInput(ou input, como visto no DOM) antes e depois da employedtroca?
Chris
@Chris Não consegui especificar que o componente MyInput renderiza a entrada envolvida em a <div>. Os data-reactidatributos parecem ser diferentes no div de agrupamento e no campo de entrada. job-titlea entrada obtém id data-reactid=".0.1.1.0.1.0.1", enquanto a unemployment-reasonentrada obtém iddata-reactid=".0.1.1.0.1.2.1"
o01
1
e sobre o quê unemployment-duration?
Chris
@Chris Desculpe, falei muito cedo. No primeiro exemplo (sem o intervalo "diff me") os reactidatributos são idênticos em job-titlee unemployment-reason, enquanto no segundo exemplo (com o intervalo diff) eles são diferentes.
o01
@Chris para unemployment-durationo reactidatributo é sempre único.
o01

Respostas:

108

O que provavelmente está acontecendo é que o React pensa que apenas um MyInput( unemployment-duration) é adicionado entre os renderizadores. Como tal, job-titlenunca é substituído por unemployment-reason, que também é o motivo pelo qual os valores predefinidos são trocados.

Quando o React faz a diferença, ele determina quais componentes são novos e quais são antigos com base em suas keypropriedades. Se nenhuma chave for fornecida no código, ele irá gerar a sua própria.

O motivo pelo qual o último trecho de código fornecido funciona é porque o React precisa essencialmente alterar a hierarquia de todos os elementos sob o pai dive acredito que isso acionaria uma nova renderização de todos os filhos (é por isso que funciona). Se você tivesse adicionado o spanna parte inferior em vez de no topo, a hierarquia dos elementos anteriores não mudaria e esses elementos não seriam renderizados novamente (e o problema persistiria).

Aqui está o que diz a documentação oficial do React :

A situação fica mais complicada quando os filhos são misturados (como nos resultados da pesquisa) ou se novos componentes são adicionados à frente da lista (como nos fluxos). Nesses casos em que a identidade e o estado de cada filho devem ser mantidos nas passagens de renderização, você pode identificar cada filho de forma exclusiva atribuindo-lhe uma chave.

Quando o React reconcilia os filhos com chave, ele garante que qualquer filho com chave seja reordenado (em vez de destruído) ou destruído (em vez de reutilizado).

Você deve ser capaz de corrigir isso fornecendo um keyelemento exclusivo para o pai divou para todos os MyInputelementos.

Por exemplo:

render(){
    if (this.state.employed) {
        return (
            <div key="employed">
                <MyInput ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div key="notEmployed">
                <MyInput ref="unemployment-reason" name="unemployment-reason" />
                <MyInput ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}

OU

render(){
    if (this.state.employed) {
        return (
            <div>
                <MyInput key="title" ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div>
                <MyInput key="reason" ref="unemployment-reason" name="unemployment-reason" />
                <MyInput key="duration" ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}

Agora, quando o React fizer a diferença, ele verá que divssão diferentes e irá renderizá-lo novamente incluindo todos os seus filhos (primeiro exemplo). No segundo exemplo, o diff será um sucesso em job-titlee unemployment-reasonuma vez que eles agora têm chaves diferentes.

É claro que você pode usar as chaves que desejar, desde que sejam exclusivas.


Atualização de agosto de 2017

Para uma melhor compreensão de como as chaves funcionam no React, recomendo fortemente a leitura da minha resposta em Compreendendo as chaves exclusivas no React.js .


Atualização novembro de 2017

Esta atualização deveria ter sido postada há algum tempo, mas o uso de literais de string refagora está obsoleto. Por exemplo ref="job-title", agora deve ser ref={(el) => this.jobTitleRef = el}(por exemplo). Consulte minha resposta ao aviso de suspensão de uso usando this.refs para obter mais informações.

Chris
fonte
10
it will determine which components are new and which are old based on their key property.- isso foi ... a chave ... para o meu entendimento
Larry
1
Muito obrigado! Eu também descobri que todos os componentes filhos de um mapa foram re-renderizados com sucesso e eu não conseguia entender por quê.
ValentinVoilean
uma boa dica é usar uma variável de estado (que muda, como um id para, por exemplo) como a chave para o div!
Macilias
200

Altere a chave do componente.

<Component key="1" />
<Component key="2" />

O componente será desmontado e uma nova instância do componente será montada, pois a chave foi alterada.

editar : Documentado sobre você Provavelmente não precisa do estado derivado :

Quando uma chave muda, o React criará uma nova instância do componente em vez de atualizar a atual. As chaves geralmente são usadas para listas dinâmicas, mas também são úteis aqui.

Alex K
fonte
45
Isso deve ser mais óbvio na documentação do React. Isso alcançou exatamente o que eu procurava, mas levou horas para encontrar.
Paul,
4
Bem, isso me ajudou muito! obrigado! Eu precisava redefinir uma animação CSS, mas a única maneira de fazer isso é forçar uma re-renderização. Concordo que isso deve ser mais óbvio na documentação de reação
Alex. P.
@ Alex.P. Agradável! As animações CSS podem ser complicadas às vezes. Eu estaria interessado em ver um exemplo.
Alex K
1
Documentação de reação que
GameSalutes
Obrigado. Estou muito grato por isso. Tentei alimentar números aleatórios em objetos não declarados como "randomNumber", mas o único objeto que funcionou foi a chave.
ShameWare
5

Use setStateem sua visão para alterar a employedpropriedade do estado. Este é um exemplo de mecanismo de renderização React.

 someFunctionWhichChangeParamEmployed(isEmployed) {
      this.setState({
          employed: isEmployed
      });
 }

 getInitialState() {
      return {
          employed: true
      }
 },

 render(){
    if (this.state.employed) {
        return (
            <div>
                <MyInput ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div>
                <span>Diff me!</span>
                <MyInput ref="unemployment-reason" name="unemployment-reason" />
                <MyInput ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}
Dmitriy
fonte
Eu já estou fazendo isso. A variável 'empregada' faz parte do estado dos componentes da visualização e é alterada por meio da função setState. Desculpe por não explicar isso.
o01
A variável 'empregada' deve ser propriedade do estado React. Não é óbvio em seu exemplo de código.
Dmitriy
como você muda this.state.employed? Mostre o código, por favor. Tem certeza de que usa setState no lugar adequado em seu código?
Dmitriy
O componente de visualização é um pouco mais complexo do que mostra meu exemplo. Além dos componentes MyInput, ele também renderiza um grupo de botões de rádio que controla o valor de 'empregado' com um manipulador onChange. No manipulador onChange, defino o estado do componente de visualização de acordo. A visualização é renderizada novamente quando o valor do grupo de botões de opção muda, mas o React pensa que o primeiro componente MyInput é o mesmo de antes de 'empregado' ser alterado.
o01
0

Estou trabalhando no Crud para meu aplicativo. É assim que eu fiz Got Reactstrap como minha dependência.

import React, { useState, setState } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import firebase from 'firebase';
// import { LifeCrud } from '../CRUD/Crud';
import { Row, Card, Col, Button } from 'reactstrap';
import InsuranceActionInput from '../CRUD/InsuranceActionInput';

const LifeActionCreate = () => {
  let [newLifeActionLabel, setNewLifeActionLabel] = React.useState();

  const onCreate = e => {
    const db = firebase.firestore();

    db.collection('actions').add({
      label: newLifeActionLabel
    });
    alert('New Life Insurance Added');
    setNewLifeActionLabel('');
  };

  return (
    <Card style={{ padding: '15px' }}>
      <form onSubmit={onCreate}>
        <label>Name</label>
        <input
          value={newLifeActionLabel}
          onChange={e => {
            setNewLifeActionLabel(e.target.value);
          }}
          placeholder={'Name'}
        />

        <Button onClick={onCreate}>Create</Button>
      </form>
    </Card>
  );
};

Alguns Ganchos React lá

Ruben Verster
fonte
Bem-vindo ao SO! Você pode querer revisar como escrever uma boa resposta . Adicione alguma explicação de como você resolveu a questão além de apenas postar o código.
displacedtexan