Artigo original: How to force-refresh a React child component: the easy way

Observação: no React 16, componentWillReceiveProps() deixou de ser utilizado. Se você utilizar essa versão ou versões superiores, em seu projeto, o conteúdo deste artigo pode não ser útil para você.

No mundo do React, forçar uma nova renderização é visto com maus olhos. Você deveria deixar que o DOM cuidasse de si mesmo quando o  React percebesse alterações ao state ou às props. Para seguir esses padrões, às vezes, precisamos fazer coisas que parecem um pouco sem sentido. Considere este cenário:

SPU87A7KxxBLyug8JI0ZKYNqZH84EGfvO78o
Exemplo simples e sem muito sentido de um componente filho que gerencia seu próprio state

Temos dois componentes — pai e filho. O pai faz uma chamada à API para obter o objeto user. A partir daí, obtemos coisas como name, age, favorite color (nome, idade e cor favorita em português, respectivamente). Também obtemos um id de nosso banco de dados. Passamos isso ao nosso componente filho, que também fará uma chamada à API, com o id do usuário. Ótimo — uma porção de dados chegando à nossa aplicação.

Vamos supor que estamos armazenando uma lista de sapatos no banco de dados. Quando o usuário altera sua cor favorita, o servidor escreve novos dados na lista de sapatos do usuário. Ótimo, não fosse pelo fato de não estarmos vendo a nova lista de sapatos em nosso componente filho. O que ocorre?

Nota à parte: logicamente, devemos apenas obter os sapatos da chamada para o usuário — essa é apenas uma explicação simplificada.

Básico da re-renderização em React

A versão resumida do que ocorreu é o fato de que o React atualizará apenas as partes do DOM que foram alteradas. Neste caso, as props que passamos para o componente sapato ( userId) não mudaram. Desse modo, nada mudou em nosso componente filho.

A preferência de cor para o usuário será atualizada quando recebermos de volta novas informações da API — levando em conta que estamos fazendo algo com a  resposta após atualizarmos um usuário.

No entanto, como o React não vê motivo para atualizar a lista de sapatos, ela não será atualizada — mesmo que, em nosso servidor, os sapatos agora sejam diferentes.

O código inicial

const UserShow extends Component {
  state = {
    user: {}
  }
  
  componentDidMount() {
    this.fetchUser().then(this.refreshUser)
  }
  
  setNewColor = color => {
    this.updateUser({color}).then(this.refreshUser)
  }
  
  refreshUser = res => this.setState({user: res.data.user})
  
  render() {
    const { user } = this.state;
    
    return (
      <div>
        User name: {user.name}
        Pick color: 
        <div>
          {colors.map(color => 
            <div className={color} 
                 onClick={() => this.setNewColor(color)} />)}
          )}
        </div>
        <ShoeList id={user.id} />
      </div>
    )
  }
}

Nossa ShoeList (lista de sapatos, em português) será apenas uma lista de sapatos, que obteremos do servidor (por fetch) com o id do usuário:

const ShoeList extends Component {
  state = {
    shoes: []
  }
  
  componentDidMount() {
    this.fetchShoes(this.props.id)
        .then(this.refreshShoeList)
  }

  refreshShoeList = res => this.setState({ shoes: res.data.shoes })
  
  render() {
    // lista de sapatos
  }
}

Se quisermos que o componente sapato obtenha a lista nova de sapatos, precisamos atualizar as props que enviamos a ele. Do contrário, ele não verá motivo para uma atualização.

De fato, pelo modo como está escrita, ShoeList nunca atualizará, já que ela não depende de props para sua renderização. Vamos dar um jeito nisso.

Fazendo com que um componente filho renderize novamente

Para forçar o componente filho a renderizar novamente — e a fazer uma nova chamada à  API — precisamos passar uma prop que alterará se a preferência de cores do usuário mudar.

Para fazermos isso, adicionamos um método em setNewColor:

[...]

setNewColor = color => {
  this.updateUser({color}).then(res => {
    this.refreshUser(res);
    this.refreshShoeList();
  })
}

refreshShoeList = () => 
  this.setState({refreshShoeList: !this.state.refreshShoeList})
  
[...]

<ShoeList id={user.id} refresh={refreshShoeList}

Essa nada mais é que uma chave que podemos girar. Tentei manter as coisas bastante simples. Em produção, no entanto, seria interessante garantir que a cor que estamos definindo seja diferente da cor que tínhamos antes. Do contrário, não teremos nada para atualizar.

Agora, em ShoeList:

componentWillReceiveProps(props) {
  const { refresh, id } = this.props;
  if (props.refresh !== refresh) {
    this.fetchShoes(id)
      .then(this.refreshShoeList)
  }
}

Se você passar apenas refreshShoeList, e a "chave", com base naquele booleano, o componente simplesmente atualizará a todo instante.

Precisamos garantir que a chave seja girada uma única vez — assim, conferiremos as props apenas quando entram e são diferentes das props que tínhamos antes. Se forem diferentes, faremos uma nova chamada à API para obter a nova lista de sapatos.

Aí está — nosso componente filho foi "forçado" a atualizar.

componentWillReceiveProps

Vale a penas dedicar um minuto a mais para analisar o que ocorre naquela última parte do código. Em componentWillReceiveProps, temos nossa única oportunidade de ver as novas props ao chegarem e de compará-las com as props anteriores.

Aqui, podemos detectar as alterações (como em refresh) e também podemos fazer verificações de novas props (observe, por exemplo, que refresh inicialmente é undefined).

Este método do React é uma maneira muito poderosa de manipular e examinar props.