Rendre le composant React lorsque l'accessoire change

90

J'essaie de séparer un composant de présentation d'un composant de conteneur. J'ai un SitesTableet un SitesTableContainer. Le conteneur est chargé de déclencher des actions de redux pour récupérer les sites appropriés en fonction de l'utilisateur actuel.

Le problème est que l'utilisateur actuel est extrait de manière asynchrone, une fois que le composant conteneur est rendu initialement. Cela signifie que le composant conteneur ne sait pas qu'il a besoin de réexécuter le code dans sa componentDidMountfonction qui mettrait à jour les données à envoyer au SitesTable. Je pense que j'ai besoin de rendre à nouveau le composant conteneur lorsque l'un de ses accessoires (utilisateur) change. Comment faire cela correctement?

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
la source
vous avez d'autres fonctions disponibles, comme componentDidUpdate, ou probablement celle que vous recherchez, componentWillReceiveProps (nextProps) si vous voulez déclencher quelque chose lorsque les accessoires changent
thsorens
Pourquoi avez-vous besoin de refaire le rendu de SitesTable s'il ne change pas ses accessoires?
QoP
@QoP les actions distribuées componentDidMountchangeront le sitesnœud dans l'état de l'application, qui est passé dans le SitesTable. Le sitesnœud de SitesStable va changer.
David
Oh, je comprends, je vais écrire la réponse.
QoP
1
Comment y parvenir dans un composant fonctionnel
yaswanthkoneri

Réponses:

115

Vous devez ajouter une condition dans votre componentDidUpdateméthode.

L'exemple utilise fast-deep-equalpour comparer les objets.

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

Utiliser des crochets (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}/>
  )

}

Si l'accessoire que vous comparez est un objet ou un tableau, vous devez utiliser à la useDeepCompareEffectplace de useEffect.

QoP
la source
Notez que JSON.stringify ne peut être utilisé que pour ce type de comparaison, s'il est stable (par spécification ce n'est pas le cas), il produit donc la même sortie pour les mêmes entrées. Je recommande de comparer les propriétés id des objets utilisateur, ou de passer userId-s dans les accessoires, et de les comparer, pour éviter des recharges inutiles.
László Kardinál
4
Veuillez noter que la méthode de cycle de vie componentWillReceiveProps est obsolète et sera probablement supprimée dans React 17. L'utilisation d'une combinaison de componentDidUpdate et de la nouvelle méthode getDerivedStateFromProps est la stratégie suggérée par l'équipe de développement de React. Plus dans leur article de blog: reactjs.org/blog/2018/03/27/update-on-async-rendering.html
michaelpoltorak
@QoP Le deuxième exemple, avec React Hooks, va-t-il être démonté et remonté à chaque userchangement? Combien ça coûte?
Robotron
30

ComponentWillReceiveProps()va être obsolète à l'avenir en raison de bogues et d'incohérences. Une solution alternative pour re-rendre un composant sur le changement d'accessoires est d'utiliser ComponentDidUpdate()et ShouldComponentUpdate().

ComponentDidUpdate()est appelée chaque fois que le composant est mis à jour ET si ShouldComponentUpdate()renvoie true (si ShouldComponentUpdate()n'est pas défini, il retourne truepar défaut).

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

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

Ce même comportement peut être accompli en utilisant uniquement la ComponentDidUpdate()méthode en incluant l'instruction conditionnelle à l'intérieur de celle-ci.

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

Si l'on tente de définir l'état sans conditionnel ou sans définir ShouldComponentUpdate()le composant, le rendu sera infiniment nouveau

ob1
la source
2
Cette réponse doit être votée (au moins pour le moment) car elle componentWillReceivePropsest sur le point d'être obsolète et est suggérée contre toute utilisation.
AnBisw
La deuxième forme (instruction conditionnelle à l'intérieur de componentDidUpdate) fonctionne pour moi parce que je veux que d'autres changements d'état se produisent encore, par exemple la fermeture d'un message flash.
Little Brain
11

Vous pouvez utiliser KEYune clé unique (combinaison des données) qui change avec les accessoires, et ce composant sera rendu avec les accessoires mis à jour.

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

Je pense que c'est l'événement dont vous avez besoin. componentWillReceivePropsse déclenche chaque fois que votre composant reçoit quelque chose via des accessoires. À partir de là, vous pouvez avoir votre vérification puis faire ce que vous voulez.

mr.b
la source
11
componentWillReceivePropsobsolète *
Maihan Nijat
2

Je recommanderais de jeter un œil à ma réponse et de voir si elle est pertinente par rapport à ce que vous faites. Si je comprends votre vrai problème, c'est que vous n'utilisez pas correctement votre action asynchrone et mettez à jour le redux "store", qui mettra automatiquement à jour votre composant avec ses nouveaux accessoires.

Cette section de votre code:

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

Ne doit pas se déclencher dans un composant, il doit être traité après l'exécution de votre première requête.

Jetez un œil à cet exemple 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))
    );
  };
}

Vous n'êtes pas nécessairement obligé d'utiliser redux-thunk, mais cela vous aidera à raisonner sur des scénarios comme celui-ci et à écrire le code correspondant.

TameBadger
la source
droit, je comprends cela. Mais où expédiez-vous exactement le makeASandwichWithSecretSauce dans votre composant?
David
Je vais vous lier à un dépôt avec un exemple pertinent, utilisez-vous react-router avec votre application?
TameBadger
@David apprécierait également le lien vers cet exemple, j'ai essentiellement le même problème.
SamYoungNY
0

Une méthode conviviale à utiliser est la suivante, une fois que le prop est mis à jour, le composant sera automatiquement rendu:

render {

let textWhenComponentUpdate = this.props.text 

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

}
0x01Brain
la source