Ri-renderizza il componente React quando cambia l'elica


95

Sto cercando di separare un componente di presentazione da un componente contenitore. Ho un SitesTablee un SitesTableContainer. Il contenitore è responsabile dell'attivazione delle azioni di redux per recuperare i siti appropriati in base all'utente corrente.

Il problema è che l'utente corrente viene recuperato in modo asincrono, dopo che il componente contenitore viene visualizzato inizialmente. Ciò significa che il componente contenitore non sa di dover rieseguire il codice nella sua componentDidMountfunzione che aggiorna i dati da inviare al file SitesTable. Penso di aver bisogno di rieseguire il rendering del componente contenitore quando uno dei suoi oggetti di scena (utente) cambia. Come posso farlo correttamente?

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);

hai altre funzioni disponibili, come componentDidUpdate, o probabilmente quella che stai cercando, componentWillReceiveProps (nextProps) se vuoi sparare qualcosa quando gli oggetti di scena cambiano
thsorens

Perché è necessario rieseguire il rendering di SitesTable se non sta cambiando i suoi oggetti di scena?
QoP

@QoP le azioni inviate componentDidMountcambieranno il sitesnodo nello stato dell'applicazione, che viene passato nel file SitesTable. Il sitesnodo di SitesStable cambierà.
David

Oh, ho capito, scriverò la risposta.
QoP

1
Come ottenere questo risultato in un componente funzionale
yaswanthkoneri

Risposte:


123

Devi aggiungere una condizione nel tuo componentDidUpdatemetodo.

L'esempio utilizza fast-deep-equalper confrontare gli oggetti.

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))
  }  
}

Usare gli Hooks (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 l'oggetto che stai confrontando è un oggetto o un array, dovresti usare useDeepCompareEffectinvece di useEffect.


Nota che JSON.stringify può essere utilizzato solo per questo tipo di confronto, se è stabile (per specifica non lo è), quindi produce lo stesso output per gli stessi input. Raccomando di confrontare le proprietà id degli oggetti utente o di passare userId-s negli oggetti di scena e di confrontarli, per evitare ricaricamenti non necessari.
László Kardinál

4
Tieni presente che il metodo del ciclo di vita componentWillReceiveProps è deprecato e probabilmente verrà rimosso in React 17. L'uso di una combinazione di componentDidUpdate e il nuovo metodo getDerivedStateFromProps è la strategia suggerita dal team di sviluppo di React. Maggiori informazioni nel loro post sul blog: reactjs.org/blog/2018/03/27/update-on-async-rendering.html
michaelpoltorak

@QoP Il secondo esempio, con React Hooks, verrà smontato e rimontato ogni volta che usercambia? Quanto costa questo?
Robotron

32

ComponentWillReceiveProps()sarà deprecato in futuro a causa di bug e incongruenze. Una soluzione alternativa per rieseguire il rendering di un componente sulla modifica degli oggetti di scena consiste nell'usare ComponentDidUpdate()e ShouldComponentUpdate().

ComponentDidUpdate()viene chiamato ogni volta che il componente si aggiorna E se ShouldComponentUpdate()restituisce true (se ShouldComponentUpdate()non è definito restituisce trueper impostazione predefinita).

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

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

Questo stesso comportamento può essere ottenuto utilizzando solo il ComponentDidUpdate()metodo includendo l'istruzione condizionale al suo interno.

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

Se si tenta di impostare lo stato senza un condizionale o senza definire ShouldComponentUpdate()il componente verrà ripetuto all'infinito


2
Questa risposta deve essere votata (almeno per ora) poiché componentWillReceivePropssta per essere deprecata ed è consigliata contro l'utilizzo.
AnBisw

La seconda forma (istruzione condizionale all'interno di componentDidUpdate) funziona per me perché voglio che si verifichino altri cambiamenti di stato, ad esempio la chiusura di un messaggio flash.
Little Brain

Qualcuno aveva usato shouldComponentUpdate nel mio codice e non riuscivo a capire quale fosse la causa del problema, questo lo ha chiarito.
steve moretz

14

È possibile utilizzare KEYuna chiave univoca (combinazione dei dati) che cambia con gli oggetti di scena e quel componente verrà riprodotto nuovamente con gli oggetti di scena aggiornati.


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

Penso che sia l'evento di cui hai bisogno. componentWillReceivePropssi attiva ogni volta che il tuo componente riceve qualcosa attraverso gli oggetti di scena. Da lì puoi avere il tuo controllo e poi fare quello che vuoi fare.


12
componentWillReceivePropsdeprecato *
Maihan Nijat

3

Consiglierei di dare un'occhiata a questa mia risposta e vedere se è rilevante per quello che stai facendo. Se capisco il tuo vero problema, è che semplicemente non usi correttamente la tua azione asincrona e non aggiorni lo "store" redux, che aggiornerà automaticamente il tuo componente con i suoi nuovi oggetti di scena.

Questa sezione del tuo codice:

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

Non dovrebbe essere attivato in un componente, dovrebbe essere gestito dopo aver eseguito la prima richiesta.

Dai un'occhiata a questo esempio da 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))
    );
  };
}

Non devi necessariamente usare redux-thunk, ma ti aiuterà a ragionare su scenari come questo e a scrivere codice da abbinare.


giusto, ho capito. Ma dove spedisci esattamente il makeASandwichWithSecretSauce tuo componente?
David

Ti collegherò a un repository con un esempio pertinente, usi React-Router con la tua app?
TameBadger

@ David apprezzerebbe anche il collegamento a quell'esempio, fondamentalmente ho lo stesso problema.
SamYoungNY

0

Un metodo amichevole da usare è il seguente, una volta aggiornato prop, eseguirà automaticamente il rendering del componente:

render {

let textWhenComponentUpdate = this.props.text 

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

}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.