El componente secundario de React no se actualiza después del cambio de estado principal

109

Estoy intentando crear un buen componente ApiWrapper para completar datos en varios componentes secundarios. De todo lo que he leído, esto debería funcionar: https://jsfiddle.net/vinniejames/m1mesp6z/1/

class ApiWrapper extends React.Component {

  constructor(props) {
    super(props);

    this.state = {
      response: {
        "title": 'nothing fetched yet'
      }
    };
  }

  componentDidMount() {
    this._makeApiCall(this.props.endpoint);
  }

  _makeApiCall(endpoint) {
    fetch(endpoint).then(function(response) {
      this.setState({
        response: response
      });
    }.bind(this))
  }

  render() {
    return <Child data = {
      this.state.response
    }
    />;
  }
}

class Child extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      data: props.data
    };
  }

  render() {
    console.log(this.state.data, 'new data');
    return ( < span > {
      this.state.data.title
    } < /span>);
  };
}

var element = < ApiWrapper endpoint = "https://jsonplaceholder.typicode.com/posts/1" / > ;

ReactDOM.render(
  element,
  document.getElementById('container')
);

Pero por alguna razón, parece que el componente secundario no se actualiza cuando cambia el estado principal.

¿Me estoy perdiendo de algo?

Vinnie James
fuente

Respuestas:

204

Hay dos problemas con su código.

El estado inicial de su componente secundario se establece a partir de props.

this.state = {
  data: props.data
};

Citando esta respuesta SO :

Pasar el estado inicial a un componente como un propes un anti-patrón porque el getInitialStatemétodo (en nuestro caso el constructor) solo se llama la primera vez que el componente se procesa. Nunca mas. Lo que significa que, si vuelve a renderizar ese componente pasando un valor diferente como a prop, el componente no reaccionará en consecuencia, porque el componente mantendrá el estado de la primera vez que se renderizó. Es muy propenso a errores.

Entonces, si no puede evitar tal situación, la solución ideal es usar el método componentWillReceivePropspara escuchar nuevos accesorios.

Agregar el siguiente código a su componente secundario resolverá su problema con la reproducción del componente secundario.

componentWillReceiveProps(nextProps) {
  this.setState({ data: nextProps.data });  
}

El segundo problema es con fetch.

_makeApiCall(endpoint) {
  fetch(endpoint)
    .then((response) => response.json())   // ----> you missed this part
    .then((response) => this.setState({ response }));
}

Y aquí hay un violín que funciona: https://jsfiddle.net/o8b04mLy/

Yadhu Kiran
fuente
1
"Está bien inicializar el estado basado en accesorios si sabes lo que estás haciendo" ¿Hay otras desventajas en establecer el estado a través de accesorios, además del hecho de que nextPropno activará una nueva renderización sin componentWillReceiveProps(nextProps)?
Vinnie James
11
Hasta donde yo sé, no hay otras desventajas. Pero en su caso, claramente podríamos evitar tener un estado dentro del componente secundario. El padre puede simplemente pasar datos como accesorios, y cuando el padre vuelve a renderizar con su nuevo estado, el hijo también vuelve a renderizar (con nuevos accesorios). En realidad, mantener un estado dentro del niño es innecesario aquí. Componentes puros FTW!
Yadhu Kiran
7
Para cualquiera que lea a partir de ahora, consulte static getDerivedStateFromProps(nextProps, prevState) reactjs.org/docs/…
GoatsWearHats
4
¿Hay alguna otra solución para esto que no sea componentWillReceiveProps () porque ahora está en desuso?
LearningMath
3
@LearningMath, consulte los últimos documentos de React que hablan sobre formas alternativas. Puede que tenga que reconsiderar su lógica.
Yadhu Kiran
1

Hay algunas cosas que debes cambiar.

Cuando fetchobtenga la respuesta, no es un json. Estaba buscando cómo puedo obtener este json y descubrí este enlace .

Por otro lado, debe pensar que la constructorfunción se llama solo una vez.

Por lo tanto, debe cambiar la forma en que recupera los datos en <Child>component.

Aquí, dejé un código de ejemplo: https://jsfiddle.net/emq1ztqj/

Espero que eso ayude.

slorenzo
fuente
Gracias. Aunque, mirando el ejemplo que hizo, todavía parece que Child nunca se actualiza. ¿Alguna sugerencia sobre cómo cambiar la forma en que el niño recibe los datos?
Vinnie James
2
¿Estás seguro? Veo que ese <Child>componente recupera los nuevos datos https://jsonplaceholder.typicode.com/posts/1y los vuelve a renderizar.
slorenzo
1
Sí, lo veo funcionando ahora en OSX. iOS no estaba activando la re-renderización en el navegador de la aplicación StackExchange
Vinnie James