¿Cómo forzar el remontaje de los componentes de React?

103

Digamos que tengo un componente de vista que tiene un render 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 se parece a esto:

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>
        );
    }
}

Digamos que employedes verdad. Siempre que lo cambio a falso y la otra vista se procesa, solo unemployment-durationse reinicializa. También unemployment-reasonse rellena previamente con el valor de job-title(si se dio un valor antes de que cambiara la condición).

Si cambio el marcado en la segunda rutina de renderizado a algo como esto:

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 todo funciona bien. Parece que React simplemente no logra diferenciar 'título del trabajo' y 'motivo de desempleo'.

Por favor dime qué estoy haciendo mal ...

o01
fuente
1
¿Qué hay data-reactiden cada uno de los elementos MyInput(o input, como se ve en DOM) antes y después del employedcambio?
Chris
@Chris No pude especificar que el componente MyInput presenta la entrada envuelta en un <div>. Los data-reactidatributos parecen ser diferentes tanto en el div de envoltura como en el campo de entrada. job-titlela entrada obtiene id data-reactid=".0.1.1.0.1.0.1", mientras que la unemployment-reasonentrada obtiene iddata-reactid=".0.1.1.0.1.2.1"
o01
1
y ¿qué pasa unemployment-duration?
Chris
@Chris Lo siento, hablé demasiado pronto. En el primer ejemplo (sin el intervalo "diff me"), los reactidatributos son idénticos en job-titley unemployment-reason, mientras que en el segundo ejemplo (con el intervalo diff) son diferentes.
o01
@Chris para unemployment-durationel reactidatributo es siempre único.
o01

Respuestas:

108

Lo que probablemente está sucediendo es que React piensa que solo se agrega un MyInput( unemployment-duration) entre los renders. Como tal, job-titlenunca se reemplaza por unemployment-reason, que también es la razón por la que se intercambian los valores predefinidos.

Cuando React hace la diferencia, determinará qué componentes son nuevos y cuáles son viejos en función de su keypropiedad. Si no se proporciona dicha clave en el código, generará la suya propia.

La razón por la que el último fragmento de código que proporcionas funciona es porque React esencialmente necesita cambiar la jerarquía de todos los elementos debajo del padre divy creo que eso desencadenaría una reproducción de todos los hijos (que es la razón por la que funciona). Si hubiera agregado el spanen la parte inferior en lugar de en la parte superior, la jerarquía de los elementos anteriores no cambiaría, y esos elementos no se volverían a representar (y el problema persistiría).

Esto es lo que dice la documentación oficial de React :

La situación se complica cuando se baraja a los niños (como en los resultados de búsqueda) o si se agregan nuevos componentes al principio de la lista (como en las secuencias). En estos casos en los que la identidad y el estado de cada niño deben mantenerse en los pases de procesamiento, puede identificar de forma única a cada niño asignándole una clave.

Cuando React reconcilie a los niños con clave, se asegurará de que cualquier niño con clave sea reordenado (en lugar de golpeado) o destruido (en lugar de reutilizado).

Debería poder solucionar este problema proporcionando un keyelemento único usted mismo para el padre divo para todos los MyInputelementos.

Por ejemplo:

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>
        );
    }
}

O

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>
        );
    }
}

Ahora, cuando React haga la diferencia, verá que divsson diferentes y lo volverá a renderizar, incluidos todos sus elementos secundarios (primer ejemplo). En el segundo ejemplo, el diff será un éxito job-titley unemployment-reasonya que ahora tienen diferentes claves.

Por supuesto, puede utilizar las claves que desee, siempre que sean únicas.


Actualización de agosto de 2017

Para tener una mejor idea de cómo funcionan las claves en React, recomiendo encarecidamente leer mi respuesta a Comprender las claves únicas en React.js .


Actualización de noviembre de 2017

Esta actualización debería haberse publicado hace un tiempo, pero el uso de literales de cadena refahora está en desuso. Por ejemplo ref="job-title", ahora debería ser ref={(el) => this.jobTitleRef = el}(por ejemplo). Consulte mi respuesta a la advertencia de obsolescencia usando this.refs para obtener más información.

Chris
fuente
10
it will determine which components are new and which are old based on their key property.- eso fue ... clave ... para mi comprensión
Larry
1
Muchas gracias ! También descubrí que todos los componentes secundarios de un mapa se volvieron a representar con éxito y no podía entender por qué.
ValentinVoilean
¡Una buena sugerencia es usar una variable de estado (que cambia, como una identificación para, por ejemplo) como la clave para el div!
Macilias
200

Cambie la clave del componente.

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

El componente se desmontará y se montará una nueva instancia de Componente ya que la clave ha cambiado.

editar : Documentado sobre usted probablemente no necesite un estado derivado :

Cuando cambia una clave, React creará una nueva instancia de componente en lugar de actualizar la actual. Las claves se utilizan normalmente para listas dinámicas, pero también son útiles aquí.

Alex K
fuente
45
Esto debería ser mucho más obvio en la documentación de React. Esto logró exactamente lo que buscaba, pero tomó horas encontrarlo.
Paul
4
¡Bueno, esto me ayudó mucho! ¡Gracias! Necesitaba restablecer una animación CSS, pero la única forma de hacerlo es forzar una nueva renderización. Estoy de acuerdo en que esto debería ser más obvio en la documentación de reacción
Alex. P.
@ Alex.P. ¡Agradable! Las animaciones CSS pueden ser complicadas a veces. Me interesaría ver un ejemplo.
Alex K
1
Reaccionar la documentación que describe esta técnica: reactjs.org/blog/2018/06/07/…
GameSalutes
Gracias. Estoy muy agradecido por esto. Intenté alimentar números aleatorios a accesorios no declarados como "randomNumber", pero el único accesorio que funcionó fue clave.
ShameWare
5

Úselo setStateen su vista para cambiar la employedpropiedad del estado. Este es un ejemplo del motor de renderizado 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
fuente
Ya estoy haciendo esto. La variable 'empleada' es parte del estado de los componentes de la vista y se cambia a través de su función setState. Perdón por no explicar eso.
o01
La variable 'empleada' debe ser propiedad del estado de React. No es obvio en su ejemplo de código.
Dmitriy
¿Cómo se cambia this.state.employed? Muestre el código, por favor. ¿Estás seguro de que usas setState en el lugar adecuado en tu código?
Dmitriy
El componente de vista es un poco más complejo de lo que muestra mi ejemplo. Además de los componentes MyInput, también genera un grupo de botones de radio que controla el valor de 'empleado' con un controlador onChange. En el controlador onChange, configuro el estado del componente de vista en consecuencia. La vista se vuelve a representar bien cuando cambia el valor del grupo de botones de radio, pero React cree que el primer componente MyInput es el mismo que antes de cambiar el 'empleado'.
o01
0

Estoy trabajando en Crud para mi aplicación. Así es como lo hice Got Reactstrap como mi dependencia.

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>
  );
};

Algunos React Hooks ahí

Ruben Verster
fuente
¡Bienvenido a SO! Es posible que desee revisar cómo escribir una buena respuesta . Agregue una explicación sobre cómo resolvió la pregunta más allá de simplemente publicar el código.
desplazadotexan