React: por que o componente filho não é atualizado quando o prop muda

135

Por que no exemplo de pseudo-código a seguir o Child não é renderizado novamente quando o Container altera foo.bar?

Container {
  handleEvent() {
    this.props.foo.bar = 123
  },

  render() {
    return <Child bar={this.props.foo.bar} />
}

Child {
  render() {
    return <div>{this.props.bar}</div>
  }
}

Mesmo se eu ligar forceUpdate()depois de modificar o valor em Container, Child ainda mostra o valor antigo.

Tuomas Toivonen
fonte
5
Esse código é seu? Parece que não é um válido Reagir código
Acho valor adereços não deve mudar no componente recipiente em vez disso, deve ser a mudança na componente pai por setState e que Estado deve ser mapa para os adereços contentores
Piyush Patel
Use o operador de propagação como este <Child bar = {... this.props.foo.bar} />
Sourav Singh
@AdrianWydmanski e as outras 5 pessoas que aprovaram: en.wikipedia.org/wiki/Pseudocode
David Newcomb
Os adereços do @PiyushPatel são atualizados quando um componente é renderizado novamente no local, como mostra o exemplo do pseudo-código. Outro exemplo disso é algo como usar <Route exact path="/user/:email" component={ListUserMessagePage} />, um link na mesma página atualizará os objetos sem criar uma nova instância e executar os eventos usuais do ciclo de vida.
David Newcomb

Respostas:

94

Atualize filho para ter o atributo 'chave' igual ao nome. O componente será renderizado novamente sempre que a chave for alterada.

Child {
  render() {
    return <div key={this.props.bar}>{this.props.bar}</div>
  }
}
polina-c
fonte
5
Obrigado por isso. Eu estava usando o repositório redux e não conseguia que meu componente fosse renderizado novamente até eu adicionar uma chave. (Eu usei a biblioteca uuid para gerar chave aleatória e funcionou).
Maiya
Usando ganchos, meu filho não seria renderizado novamente ao definir o estado em uma matriz existente. Meu (provavelmente má idéia) hack foi para definir simultaneamente o estado de uma prop manequim e acrescentar que para a matriz dependência useEffect: [props.notRenderingArr, props.dummy]. Se o comprimento da matriz sempre mudar, você poderá definir a matriz de dependência useEffect como [props.notRenderingArr.length].
Hipnose
5
era disso que eu também precisava. Estou confuso, quando é keynecessário, vs apenas setState? Eu tenho algumas crianças que dependem de estado pais, mas eles não estão provocando uma re-render ...
dcsan
Ótima resposta. Mas por que esse comportamento?
Rishav 15/04
1
Eu estava usando o índice como uma chave, mas não estava funcionando corretamente. Agora estou usando o uuid e é perfeito :) Obrigado a @Maiya
Ali Rehman
90

Porque os filhos não são renderizados novamente se os objetos do pai mudarem, mas se o seu ESTADO mudar :)

O que você está mostrando é o seguinte: https://facebook.github.io/react/tips/communicate-between-components.html

Ele transmitirá dados de pai para filho através de adereços, mas não há lógica de renderização lá.

Você precisa definir algum estado para o pai e renderizar novamente o filho no estado de mudança pai. Isso poderia ajudar. https://facebook.github.io/react/tips/expose-component-functions.html

François Richard
fonte
5
Por que setState causará nova renderização, mas forceUpdate não? Também pouco se destaca, mas como os componentes são atualizados quando os props são passados ​​do redux e seu estado é atualizado por meio da ação?
Tuomas Toivonen
adereços não são atualizados, mesmo se você atualizar o componente, os adereços que você passou ainda estão lá. Flux é outro tópico sim :)
François Richard
3
state! = adereços você precisa ler mais sobre isso. Você não fazer atualizações com adereços
François Richard
você pode vê-lo diretamente no código fonte, não é simplesmente o mesmo processo
Webwoman
então o que você disse aqui foi alterado ou eu não entendi direito, fiz uma pergunta a propósito, stackoverflow.com/questions/58903921/…
ILoveReactAndNode
50

Eu tive o mesmo problema. Esta é a minha solução, não tenho certeza de que seja uma boa prática, diga-me se não:

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

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

UPD: Agora você pode fazer o mesmo usando React Hooks: (somente se o componente for uma função)

const [value, setValue] = useState(propName);
// This will launch only if propName value has chaged.
useEffect(() => { setValue(propName) }, [propName]);
petrichor
fonte
3
Esta é definitivamente a resposta correta, para não mencionar o mais simples
Guilherme Ferreira
1
Eu também estou usando essa abordagem.
rawsly
@ vancy-pants componentDidUpdateNeste caso, o componente Child.
Nick Friskel 6/09/19
4
Você não precisa e não deve transformar adereços em um componente filho. Basta usar os adereços diretamente. No FunctionComponentsele irá atualizar automaticamente os componentes criança, se a mudança adereços.
oemera
1
Esta abordagem também funciona quando você adereços em componentes filhos derivado de estado pai
Chefk5
10

De acordo com a filosofia React, o componente não pode mudar seus props. eles devem ser recebidos dos pais e devem ser imutáveis. Somente os pais podem mudar os adereços de seus filhos.

boa explicação sobre estado vs adereços

Além disso, leia este tópico Por que não consigo atualizar os adereços no react.js?

Sujan Thakare
fonte
Isso também não significa que o contêiner não pode modificar os objetos que recebe do repositório redux?
Tuomas Toivonen
3
O container não recebe os objetos diretamente da loja, eles são fornecidos lendo uma parte de uma árvore de estado redux (confusa ??). !! confusão é porque não sabemos sobre a função mágica conectada pelo react-redux. se você vir o código-fonte do método connect, basicamente ele cria um componente de ordem superior que possui um estado com a propriedade storeState e é inscrito no repositório redux. À medida que o storeState muda, toda a nova renderização acontece e os acessórios modificados são fornecidos ao contêiner lendo o estado alterado. Esperamos que isso responde à pergunta
Sujan Thakare
Boa explicação, pensando componente ordem superior me ajuda a entender o que está acontecendo
Tuomas Toivonen
7

Você deve usar a setStatefunção Caso contrário, o estado não salvará sua alteração, não importa como você use o forceUpdate.

Container {
    handleEvent= () => { // use arrow function
        //this.props.foo.bar = 123
        //You should use setState to set value like this:
        this.setState({foo: {bar: 123}});
    };

    render() {
        return <Child bar={this.state.foo.bar} />
    }
    Child {
        render() {
            return <div>{this.props.bar}</div>
        }
    }
}

Seu código parece inválido. Não consigo testar esse código.

JamesYin
fonte
Não deveria ser <Child bar={this.state.foo.bar} />?
Josh Broadhurst
Certo. Eu atualizo a resposta. Mas como eu disse, isso pode não ser um código válido. Simplesmente copie da pergunta.
JamesYin
5

Quando criar componentes do React a partir de functionse useState.

const [drawerState, setDrawerState] = useState(false);

const toggleDrawer = () => {
      // attempting to trigger re-render
      setDrawerState(!drawerState);
};

Isso não funciona

         <Drawer
            drawerState={drawerState}
            toggleDrawer={toggleDrawer}
         />

Isso funciona (adicionando chave)

         <Drawer
            drawerState={drawerState}
            key={drawerState}
            toggleDrawer={toggleDrawer}
         />
Nelles
fonte
3

Confirmado, adicionar uma chave funciona. Analisei os documentos para tentar entender o porquê.

O React quer ser eficiente ao criar componentes filhos. Ele não renderizará um novo componente se for igual a outro filho, o que torna a página carregada mais rapidamente.

A adição de uma chave força o React para renderizar um novo componente, redefinindo o estado desse novo componente.

https://reactjs.org/docs/reconciliation.html#recursing-on-children

tjr226
fonte
2

Use a função setState. Então você poderia fazer

       this.setState({this.state.foo.bar:123}) 

dentro do método de evento handle.

Uma vez atualizado, o estado acionará as alterações e a re-renderização ocorrerá.

Indraneel Bende
fonte
1
Deve ser this.setState ({foo.bar:123}), certo?
Charith Jayasanka
0

Você provavelmente deve tornar o Child como componente funcional se ele não mantiver nenhum estado e simplesmente renderizar os objetos e depois chamá-lo do pai. Uma alternativa para isso é que você pode usar ganchos com o componente funcional (useState), que fará com que o componente sem estado seja renderizado novamente.

Além disso, você não deve alterar os propas, pois eles são imutáveis. Mantenha o estado do componente.

Child = ({bar}) => (bar);
satyam soni
fonte
0

Eu estava encontrando o mesmo problema. Eu tinha um Tooltipcomponente que estava recebendo showTooltipprop, que estava atualizando no Parentcomponente com base em uma ifcondição, estava sendo atualizado no Parentcomponente, mas o Tooltipcomponente não estava sendo renderizado.

const Parent = () => {
   let showTooltip = false;
   if(....){ showTooltip = true; }
   return(
      <Tooltip showTooltip={showTooltip}></Tooltip>
   )
}

O erro que eu estava cometendo é declarar showTooltipuma permissão.

Percebi que o que estava fazendo de errado estava violando os princípios de como a renderização funciona. Substituí-la por ganchos fez o trabalho.

const [showTooltip, setShowTooltip] =  React.useState<boolean>(false);
Divyanshu Rawat
fonte
0

defina adereços alterados em mapStateToProps do método connect no componente filho.

function mapStateToProps(state) {
  return {
    chanelList: state.messaging.chanelList,
  };
}

export default connect(mapStateToProps)(ChannelItem);

No meu caso, o canal de channelList é atualizado, então eu adicionei chanelList em mapStateToProps

Rajesh Nasit
fonte