Renderizar novamente o componente React quando o prop muda

95

Estou tentando separar um componente de apresentação de um componente de contêiner. Eu tenho um SitesTablee um SitesTableContainer. O contêiner é responsável por acionar ações de redux para buscar os sites apropriados com base no usuário atual.

O problema é que o usuário atual é buscado de forma assíncrona, depois que o componente do contêiner é renderizado inicialmente. Isso significa que o componente do contêiner não sabe que precisa reexecutar o código em sua componentDidMountfunção, o que atualizaria os dados a serem enviados ao SitesTable. Acho que preciso renderizar novamente o componente do contêiner quando um de seus adereços (usuário) muda. Como faço isso corretamente?

class SitesTableContainer extends React.Component {
    static get propTypes() {
      return {
        sites: React.PropTypes.object,
        user: React.PropTypes.object,
        isManager: React.PropTypes.boolean
      }
     }

    componentDidMount() {
      if (this.props.isManager) {
        this.props.dispatch(actions.fetchAllSites())
      } else {
        const currentUserId = this.props.user.get('id')
        this.props.dispatch(actions.fetchUsersSites(currentUserId))
      }  
    }

    render() {
      return <SitesTable sites={this.props.sites}/>
    }
}

function mapStateToProps(state) {
  const user = userUtils.getCurrentUser(state)

  return {
    sites: state.get('sites'),
    user,
    isManager: userUtils.isManager(user)
  }
}

export default connect(mapStateToProps)(SitesTableContainer);
David
fonte
você tem algumas outras funções disponíveis, como componentDidUpdate, ou provavelmente a que está procurando, componentWillReceiveProps (nextProps) se quiser disparar algo quando os adereços mudarem
thsorens
Por que você precisa renderizar novamente o SitesTable se ele não está mudando seus adereços?
QoP
@QoP as ações que estão sendo despachadas componentDidMountirão alterar o sitesnó no estado do aplicativo, que é passado para o SitesTable. O sitesnó do SitesStable será alterado.
David
Ah, entendi, vou escrever a resposta.
QoP
1
Como fazer isso em um componente funcional
yaswanthkoneri

Respostas:

123

Você tem que adicionar uma condição em seu componentDidUpdatemétodo.

O exemplo está usando fast-deep-equalpara comparar os objetos.

import equal from 'fast-deep-equal'

...

constructor(){
  this.updateUser = this.updateUser.bind(this);
}  

componentDidMount() {
  this.updateUser();
}

componentDidUpdate(prevProps) {
  if(!equal(this.props.user, prevProps.user)) // Check if it's a new user, you can also use some unique property, like the ID  (this.props.user.id !== prevProps.user.id)
  {
    this.updateUser();
  }
} 

updateUser() {
  if (this.props.isManager) {
    this.props.dispatch(actions.fetchAllSites())
  } else {
    const currentUserId = this.props.user.get('id')
    this.props.dispatch(actions.fetchUsersSites(currentUserId))
  }  
}

Usando Ganchos (React 16.8.0+)

import React, { useEffect } from 'react';

const SitesTableContainer = ({
  user,
  isManager,
  dispatch,
  sites,
}) => {
  useEffect(() => {
    if(isManager) {
      dispatch(actions.fetchAllSites())
    } else {
      const currentUserId = user.get('id')
      dispatch(actions.fetchUsersSites(currentUserId))
    }
  }, [user]); 

  return (
    return <SitesTable sites={sites}/>
  )

}

Se o objeto que você está comparando for um objeto ou uma matriz, você deve usar em useDeepCompareEffectvez de useEffect.

QoP
fonte
Observe que JSON.stringify só pode ser usado para esse tipo de comparação, se for estável (por especificação, não é), portanto, produz a mesma saída para as mesmas entradas. Eu recomendo comparar as propriedades de id dos objetos de usuário, ou passar userId-s nos props, e compará-los, para evitar recarregamentos desnecessários.
László Kardinál
4
Observe que o método de ciclo de vida componentWillReceiveProps está obsoleto e provavelmente será removido no React 17. Usar uma combinação de componentDidUpdate e o novo método getDerivedStateFromProps é a estratégia sugerida pela equipe de desenvolvimento de React. Mais na postagem do blog: reactjs.org/blog/2018/03/27/update-on-async-rendering.html
michaelpoltorak
@QoP O segundo exemplo, com React Hooks, ele vai desmontar e remontar sempre que usermudar? Quão caro é isso?
Robotron
32

ComponentWillReceiveProps()será preterido no futuro devido a bugs e inconsistências. Uma solução alternativa para renderizar novamente um componente na mudança de adereços é usar ComponentDidUpdate()e ShouldComponentUpdate().

ComponentDidUpdate()é chamado sempre que o componente é atualizado e se ShouldComponentUpdate()retorna verdadeiro ( ShouldComponentUpdate()se não for definido, ele retorna truepor padrão).

shouldComponentUpdate(nextProps){
    return nextProps.changedProp !== this.state.changedProp;
}

componentDidUpdate(props){
    // Desired operations: ex setting state
}

Esse mesmo comportamento pode ser realizado usando apenas o ComponentDidUpdate()método, incluindo a instrução condicional dentro dele.

componentDidUpdate(prevProps){
    if(prevProps.changedProp !== this.props.changedProp){
        this.setState({          
            changedProp: this.props.changedProp
        });
    }
}

Se alguém tentar definir o estado sem uma condição ou sem definir, ShouldComponentUpdate()o componente irá renderizar novamente

ob1
fonte
2
Esta resposta precisa ser votada positivamente (pelo menos por enquanto), pois componentWillReceivePropsestá prestes a ser descontinuada e é sugerida contra o uso.
AnBisw
A segunda forma (declaração condicional dentro de componentDidUpdate) funciona para mim porque eu quero que outras mudanças de estado ainda ocorram, por exemplo, fechando uma mensagem flash.
Little Brain
Alguém usou o shouldComponentUpdate no meu código e eu não conseguia entender o que está causando o problema, isso deixou claro.
Steve Moretz
14

Você poderia usar KEYuma chave única (combinação dos dados) que muda com os acessórios, e esse componente será renderizado novamente com os acessórios atualizados.

Rafi
fonte
4
componentWillReceiveProps(nextProps) { // your code here}

Acho que é o evento que você precisa. componentWillReceivePropsdispara sempre que seu componente recebe algo por meio de acessórios. De lá, você pode fazer sua verificação e fazer o que quiser.

sr.b
fonte
12
componentWillReceivePropsobsoleto *
Maihan Nijat
3

Eu recomendaria dar uma olhada nesta resposta minha e ver se ela é relevante para o que você está fazendo. Se eu entendi seu verdadeiro problema, é que você simplesmente não está usando sua ação assíncrona corretamente e atualizando a "loja" redux, que irá atualizar automaticamente seu componente com seus novos adereços.

Esta seção do seu código:

componentDidMount() {
      if (this.props.isManager) {
        this.props.dispatch(actions.fetchAllSites())
      } else {
        const currentUserId = this.props.user.get('id')
        this.props.dispatch(actions.fetchUsersSites(currentUserId))
      }  
    }

Não deve ser acionado em um componente, deve ser tratado após a execução de sua primeira solicitação.

Dê uma olhada neste exemplo de redux-thunk :

function makeASandwichWithSecretSauce(forPerson) {

  // Invert control!
  // Return a function that accepts `dispatch` so we can dispatch later.
  // Thunk middleware knows how to turn thunk async actions into actions.

  return function (dispatch) {
    return fetchSecretSauce().then(
      sauce => dispatch(makeASandwich(forPerson, sauce)),
      error => dispatch(apologize('The Sandwich Shop', forPerson, error))
    );
  };
}

Você não precisa necessariamente usar redux-thunk, mas isso o ajudará a raciocinar sobre cenários como este e escrever um código correspondente.

TameBadger
fonte
certo, eu entendo isso. Mas para onde exatamente você despacha o makeASandwichWithSecretSauce em seu componente?
David
Vou vincular você a um repo com um exemplo relevante, você usa o react-router com seu aplicativo?
TameBadger
@David também gostaria de receber o link para esse exemplo, eu tenho basicamente o mesmo problema.
SamYoungNY
0

Um método amigável para usar é o seguinte, assim que o prop for atualizado, ele irá renderizar novamente o componente automaticamente:

render {

let textWhenComponentUpdate = this.props.text 

return (
<View>
  <Text>{textWhenComponentUpdate}</Text>
</View>
)

}
0x01Brain
fonte