O componente filho React não é atualizado após a alteração do estado pai

109

Estou tentando fazer um bom componente ApiWrapper para preencher dados em vários componentes filhos. De tudo que li, isso deve 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')
);

Mas, por algum motivo, parece que o componente filho não está atualizando quando o estado pai muda.

Estou faltando alguma coisa aqui?

Vinnie James
fonte

Respostas:

203

Existem dois problemas com o seu código.

O estado inicial do seu componente filho é definido a partir de adereços.

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

Citando esta resposta SO :

Passar o estado inicial para um componente como um propé um antipadrão porque o getInitialStatemétodo (em nosso caso, o constutor) só é chamado na primeira vez que o componente é renderizado. Nunca mais. O que significa que, se você renderizar novamente esse componente passando um valor diferente como um prop, o componente não reagirá de acordo, porque o componente manterá o estado desde a primeira vez que foi renderizado. É muito sujeito a erros.

Portanto, se você não puder evitar tal situação, a solução ideal é usar o método componentWillReceivePropspara ouvir novos adereços.

Adicionar o código abaixo ao seu componente filho resolverá seu problema com a re-renderização do componente filho.

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

O segundo problema é com o fetch.

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

E aqui está um violino funcionando: https://jsfiddle.net/o8b04mLy/

Yadhu Kiran
fonte
1
"Não há problema em inicializar o estado com base em props se você souber o que está fazendo" Existem outras desvantagens em definir o estado via prop, além do fato de que nextPropnão aciona uma nova renderização sem componentWillReceiveProps(nextProps)?
Vinnie James
11
Pelo que eu sei, não há outras desvantagens. Mas, no seu caso, podemos evitar claramente um estado dentro do componente filho. O pai pode apenas passar os dados como adereços, e quando o pai é renderizado novamente com seu novo estado, o filho também renderiza novamente (com novos adereços). Na verdade, manter um estado dentro da criança é desnecessário aqui. Componentes puros FTW!
Yadhu Kiran
7
Para quem está lendo de agora em diante, verifique static getDerivedStateFromProps(nextProps, prevState) reactjs.org/docs/…
GoatsWearHats
4
Existe alguma outra solução para isso diferente de componentWillReceiveProps () porque ele está obsoleto agora?
LearningMath de
3
@LearningMath, consulte os documentos mais recentes do React, que falam sobre formas alternativas. Você pode ter que reconsiderar sua lógica.
Yadhu Kiran
1

Existem algumas coisas que você precisa mudar.

Ao fetchobter a resposta, não é um json. Eu estava procurando como posso obter este json e descobri este link .

Por outro lado, você precisa pensar que a constructorfunção é chamada apenas uma vez.

Portanto, você precisa alterar a maneira como recupera os dados no <Child>componente.

Aqui, deixei um código de exemplo: https://jsfiddle.net/emq1ztqj/

Espero que ajude.

Slorenzo
fonte
Obrigado. Embora, olhando para o exemplo que você fez, ainda pareça que Child nunca atualiza. Alguma sugestão sobre como mudar a maneira como a criança recebe os dados?
Vinnie James
2
Você tem certeza? Vejo que o <Child>componente recupera os novos dados https://jsonplaceholder.typicode.com/posts/1e os processa novamente.
slorenzo de
1
Sim, vejo que está funcionando agora no OSX. O iOS não estava acionando a re-renderização no navegador do aplicativo StackExchange
Vinnie James