Mise à jour de l'état lors du changement d'accessoires dans React Form

184

J'ai des problèmes avec un formulaire React et je gère correctement l'état. J'ai un champ de saisie de temps dans un formulaire (dans un modal). La valeur initiale est définie comme une variable d'état dans getInitialStateet est transmise à partir d'un composant parent. Cela fonctionne bien en soi.

Le problème survient lorsque je souhaite mettre à jour la valeur start_time par défaut via le composant parent. La mise à jour elle-même se produit dans le composant parent via setState start_time: new_time. Cependant, dans ma forme, la valeur par défaut start_time ne change jamais, car elle n'est définie qu'une seule fois getInitialState.

J'ai essayé de componentWillUpdateforcer un changement d'état à travers setState start_time: next_props.start_time, ce qui a effectivement fonctionné, mais m'a donné des Uncaught RangeError: Maximum call stack size exceedederreurs.

Ma question est donc la suivante: quelle est la bonne façon de mettre à jour l'état dans ce cas? Est-ce que je pense mal à cela d'une manière ou d'une autre?

Code actuel:

@ModalBody = React.createClass
  getInitialState: ->
    start_time: @props.start_time.format("HH:mm")

  #works but takes long and causes:
  #"Uncaught RangeError: Maximum call stack size exceeded"
  componentWillUpdate: (next_props, next_state) ->
    @setState(start_time: next_props.start_time.format("HH:mm"))

  fieldChanged: (fieldName, event) ->
    stateUpdate = {}
    stateUpdate[fieldName] = event.target.value
    @setState(stateUpdate)

  render: ->
    React.DOM.div
      className: "modal-body"
      React.DOM.form null,
        React.createElement FormLabelInputField,
          type: "time"
          id: "start_time"
          label_name: "Start Time"
          value: @state.start_time
          onChange: @fieldChanged.bind(null, "start_time”)

@FormLabelInputField = React.createClass
  render: ->
    React.DOM.div
      className: "form-group"
      React.DOM.label
        htmlFor: @props.id
        @props.label_name + ": "
      React.DOM.input
        className: "form-control"
        type: @props.type
        id: @props.id
        value: @props.value
        onChange: @props.onChange
David Basalla
la source

Réponses:

287

componentWillReceiveProps est dépecré depuis react 16: utilisez plutôt getDerivedStateFromProps

Si je comprends bien, vous avez un composant parent qui est transmis start_timeau ModalBodycomposant qui lui attribue son propre état? Et vous voulez mettre à jour cette heure à partir du parent, pas d'un composant enfant.

React a quelques astuces pour gérer ce scénario. (Notez qu'il s'agit d'un ancien article qui a depuis été supprimé du Web. Voici un lien vers la documentation actuelle sur les accessoires de composants ).

L'utilisation d'accessoires pour générer un état getInitialStateconduit souvent à une duplication de la «source de vérité», c'est-à-dire où se trouvent les données réelles. En effet, il getInitialStaten'est appelé que lorsque le composant est créé pour la première fois.

Dans la mesure du possible, calculez les valeurs à la volée pour vous assurer qu'elles ne se désynchronisent pas plus tard et ne causent pas de problèmes de maintenance.

Fondamentalement, chaque fois que vous attribuez un parent propsà un enfant, statela méthode de rendu n'est pas toujours appelée lors de la mise à jour de l'accessoire. Vous devez l'invoquer manuellement, en utilisant la componentWillReceivePropsméthode.

componentWillReceiveProps(nextProps) {
  // You don't have to do this check first, but it can help prevent an unneeded render
  if (nextProps.startTime !== this.state.startTime) {
    this.setState({ startTime: nextProps.startTime });
  }
}
Brad B
la source
84
Obsolète à partir de React 16
dude
7
@dude Ce n'est pas encore obsolète, ce à quoi vous faites référence n'est qu'un avertissement pour référence future. Je cite[..]going to be deprecated in the future
paddotk
7
@poepje Il n'est peut-être pas encore obsolète, mais il est considéré comme dangereux par la norme actuelle et devrait probablement être évité
unflores
12
Alors, quelle devrait être la nouvelle façon de faire cela après que componentWillReceiveProps soit obsolète?
Boris D. Teoharov
5
@Boris Maintenant, l'équipe de réaction vous dit essentiellement de vous faire bourrer. Ils vous donnent une nouvelle méthode, appelée getDerivedStateFromProps. Le hic, c'est qu'il s'agit d'une méthode statique. Cela signifie que vous ne pouvez rien faire de manière asynchrone pour mettre à jour l'état (car vous devez renvoyer le nouvel état immédiatement), vous ne pouvez pas non plus accéder aux méthodes de classe ou aux champs. Vous pouvez également utiliser la mémorisation, mais cela ne convient pas à tous les cas d'utilisation. Une fois de plus, l'équipe de réaction veut forcer sa façon de faire les choses. C'est une décision de conception extrêmement stupide et incapacitante.
ig-dev
76

Apparemment, les choses changent ... getDerivedStateFromProps () est maintenant la fonction préférée.

class Component extends React.Component {
  static getDerivedStateFromProps(props, current_state) {
    if (current_state.value !== props.value) {
      return {
        value: props.value,
        computed_prop: heavy_computation(props.value)
      }
    }
    return null
  }
}

(ci-dessus code par danburzo @ github)

ErichBSchulz
la source
7
Pour info, vous devez également revenir nullsi rien ne devrait changer, donc juste après votre si, vous devriez y aller avecreturn null
Ilgıt Yıldırım
@ IlgıtYıldırım - ont édité le code depuis que 4 personnes ont voté pour votre commentaire - cela fait-il vraiment une différence?
ErichBSchulz
Il existe une très bonne ressource qui explique en détail différentes options et pourquoi vous utiliseriez l'une getDerivedStateFromPropsou l' autre ou la mémorisation reactjs.org/blog/2018/06/07/…
unflores
2
getDerivedStateFromProps est forcé d'être statique. Cela signifie que vous ne pouvez rien faire de manière asynchrone pour mettre à jour l'état, ni accéder aux méthodes de classe ou aux champs. Une fois de plus, l'équipe de réaction veut forcer sa façon de faire les choses. C'est une décision de conception extrêmement stupide et incapacitante.
ig-dev
39

componentWillReceiveProps est obsolète car son utilisation "conduit souvent à des bogues et des incohérences".

Si quelque chose change de l'extérieur, envisagez de réinitialiser entièrement le composant enfant aveckey .

Fournir un keyaccessoire au composant enfant garantit que chaque fois que la valeur des keychangements de l'extérieur, ce composant est re-rendu. Par exemple,

<EmailInput
  defaultEmail={this.props.user.email}
  key={this.props.user.id}
/>

Sur ses performances:

Bien que cela puisse sembler lent, la différence de performance est généralement insignifiante. L'utilisation d'une clé peut même être plus rapide si les composants ont une logique lourde qui s'exécute sur les mises à jour, car la différence est contournée pour ce sous-arbre.

Lucie
la source
1
La clé, le secret! Fonctionne parfaitement dans React 16 comme mentionné ci-dessus
Darren Sweeney
key ne fonctionnera pas, si c'est un objet et que vous n'avez pas de chaîne unique
user3468806
Key fonctionne pour les objets, je l'ai fait. Bien sûr, j'avais une chaîne unique pour la clé.
tsujp
@ user3468806 S'il ne s'agit pas d'un objet complexe avec des références externes, vous pouvez utiliser JSON.stringify(myObject)pour dériver une clé unique de votre objet.
Roy Prins
24

ComponentDidUpdate est également disponible.

Signatur de fonction:

componentDidUpdate(prevProps, prevState, snapshot)

Profitez-en pour opérer sur le DOM lorsque le composant a été mis à jour. N'est pas appelé lors de l'initiale render.

Voir que vous n'avez probablement pas besoin d'un article sur l' état dérivé , qui décrit Anti-Pattern pour les deux componentDidUpdateet getDerivedStateFromProps. Je l'ai trouvé très utile.

arminfro
la source
Je finis par utiliser componentDidUpdateparce que c'est simple et c'est plus adapté à la plupart des cas.
KeitelDOG
14

La nouvelle façon de procéder avec hooks consiste à utiliser useEffect au lieu de componentWillReceiveProps à l'ancienne:

componentWillReceiveProps(nextProps) {
  // You don't have to do this check first, but it can help prevent an unneeded render
  if (nextProps.startTime !== this.state.startTime) {
    this.setState({ startTime: nextProps.startTime });
  }
}

devient le suivant dans un composant piloté par des crochets fonctionnels:

// store the startTime prop in local state
const [startTime, setStartTime] = useState(props.startTime)
// 
useEffect(() => {
  if (props.startTime !== startTime) {
    setStartTime(props.startTime);
  }
}, [props.startTime]);

nous définissons l'état en utilisant setState, en utilisant useEffect, nous vérifions les modifications apportées au prop spécifié, et prenons l'action pour mettre à jour l'état lors du changement de prop.

MMO
la source
5

Vous n'avez probablement pas besoin d'un état dérivé

1. Définissez une clé du parent

Lorsqu'une clé change, React créera une nouvelle instance de composant plutôt que de mettre à jour l'actuelle. Les clés sont généralement utilisées pour les listes dynamiques mais sont également utiles ici.

2. Utilisez getDerivedStateFromProps/componentWillReceiveProps

Si la clé ne fonctionne pas pour une raison quelconque (peut-être que le composant est très coûteux à initialiser)

En utilisant, getDerivedStateFromPropsvous pouvez réinitialiser n'importe quelle partie de l'état mais cela semble un peu bogué pour le moment (v16.7) !, voir le lien ci-dessus pour l'utilisation

Ghominejad
la source
2

De la documentation de réaction: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html

L'effacement de l'état lorsque les accessoires changent est un Anti Pattern

Depuis React 16, componentWillReceiveProps est obsolète. À partir de la documentation de react, l'approche recommandée dans ce cas est l'utilisation

  1. Composant entièrement contrôlé: ParentComponentla ModalBodyvolonté de posséder l' start_timeétat. Ce n'est pas mon approche préférée dans ce cas car je pense que le modal devrait posséder cet état.
  2. Composant totalement incontrôlé avec une clé: c'est mon approche préférée. Un exemple tiré de la documentation de react : https://codesandbox.io/s/6v1znlxyxn . Vous possédez entièrement l' start_timeétat de votre ModalBodyet l'utiliser getInitialStatecomme vous l'avez déjà fait. Pour réinitialiser l' start_timeétat, il vous suffit de changer la clé duParentComponent
Lu Tran
la source
0

Utiliser Memoize

La dérivation d'état de l'op est une manipulation directe des accessoires, sans véritable dérivation nécessaire. En d'autres termes, si vous avez un accessoire qui peut être utilisé ou transformé directement, il n'est pas nécessaire de stocker l'accessoire sur l'état .

Etant donné que la valeur d'état de start_timeest simplement le prop start_time.format("HH:mm"), les informations contenues dans le prop sont déjà en soi suffisantes pour mettre à jour le composant.

Cependant, si vous ne vouliez appeler le format que sur un changement d'accessoire, la bonne façon de le faire selon la dernière documentation serait via Memoize: https://reactjs.org/blog/2018/06/07/you-probably-dont- état-dérivé du besoin.html # what-about-memoization

DannyMoshe
la source
-1

Je pense que l'utilisation de ref est sans danger pour moi, je n'ai pas besoin de vous soucier de la méthode ci-dessus.

class Company extends XComponent {
    constructor(props) {
        super(props);
        this.data = {};
    }
    fetchData(data) {
        this.resetState(data);
    }
    render() {
        return (
            <Input ref={c => this.data['name'] = c} type="text" className="form-control" />
        );
    }
}
class XComponent extends Component {
    resetState(obj) {
        for (var property in obj) {
            if (obj.hasOwnProperty(property) && typeof this.data[property] !== 'undefined') {
                if ( obj[property] !== this.data[property].state.value )
                    this.data[property].setState({value: obj[property]});
                else continue;
            }
            continue;
        }
    }
}
Météo de mai VN
la source
Je pense que cette réponse est cryptique (le code est à peine lisible et sans aucune explication / lien avec le problème d'OP) et ne s'attaque pas au problème d'OP, qui est de savoir comment gérer l'état initial.
netchkin