web-dev-qa-db-fra.com

Rendu du composant React quand prop change

J'essaie de séparer un composant de présentation d'un composant de conteneur. J'ai un SitesTable et un SitesTableContainer. Le conteneur est responsable du déclenchement des actions redux pour extraire les sites appropriés en fonction de l'utilisateur actuel.

Le problème est que l'utilisateur actuel est extrait de manière asynchrone, après le rendu initial du composant conteneur. Cela signifie que le composant conteneur ne sait pas qu'il doit ré-exécuter le code dans sa fonction componentDidMount pour mettre à jour les données à envoyer au SitesTable. Je pense que je dois re-rendre le composant conteneur lorsque l'un de ses accessoires (utilisateur) change. Comment est-ce que je fais ceci 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);
54
David

Vous devez ajouter une condition dans votre méthode componentDidUpdate.

L'exemple utilise fast-deep-equal pour 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))
  }  
}
_

Utilisation de 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 useDeepCompareEffect au lieu de useEffect .

74
QoP

ComponentWillReceiveProps() sera obsolète à cause de bugs et d'incohérences. Une autre solution pour retransformer un composant sur un accessoire est d'utiliser ComponentDidUpdate() et ShouldComponentUpdate().

ComponentDidUpdate() est appelé à chaque fois que le composant met à jour AND si ShouldComponentUpdate() renvoie true (si ShouldComponentUpdate() n'est pas défini, il renvoie true par 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 méthode ComponentDidUpdate() 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 essaie de définir l'état sans condition ou sans définir ShouldComponentUpdate(), le composant sera restitué à l'infini

14
ob1
componentWillReceiveProps(nextProps) { // your code here}

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

5
mr.b

Je recommanderais de jeter un coup d'œil à ceci réponse à moi, et de voir si cela est pertinent par rapport à ce que vous faites. Si je comprends votre problème, c’est que vous n’utilisez tout simplement pas votre action asynchrone correctement et que vous mettez à jour le "magasin" redux, 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 devrait pas déclencher dans un composant, il devrait être traité après avoir exécuté votre première demande.

Regardez 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'avez pas nécessairement besoin d'utiliser redux-thunk, mais cela vous aidera à raisonner sur de tels scénarios et à écrire du code correspondant.

2
TameBadger

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

1
Rafi

Une méthode conviviale à utiliser est la suivante: une fois que l'hôte a été mis à jour, il rendra automatiquement le composant:

render {

let textWhenComponentUpdate = this.props.text 

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

}
0
0x01Brain