web-dev-qa-db-fra.com

Mise à jour de l'état des accessoires dans React Form

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 en tant que variable d'état dans getInitialState et 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 fiche, la valeur par défaut start_time ne change jamais, car elle n'est définie qu'une fois dans getInitialState.

J'ai essayé d'utiliser componentWillUpdate pour forcer un changement d'état via setState start_time: next_props.start_time, qui a fonctionné, mais m'a donné des erreurs Uncaught RangeError: Maximum call stack size exceeded.

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 à ce qui ne va pas?

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
145
David Basalla

Si j'ai bien compris, vous avez un composant parent qui transmet start_time au composant ModalBody qui l'assigne à 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. (Remarque: il s'agit d'un vieil article qui a depuis été supprimé du Web. Voici un lien vers l'actuel doc sur les accessoires de composant =).

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

Autant que possible, calculez les valeurs à la volée pour vous assurer qu'elles ne seront pas désynchronisées plus tard et ne causeront pas de problèmes de maintenance.

Fondamentalement, chaque fois que vous affectez la variable props d'un parent à la variable state d'un enfant, la méthode de rendu n'est pas toujours appelée lors de la mise à jour prop. Vous devez l'invoquer manuellement à l'aide de la méthode componentWillReceiveProps.

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 });
  }
}
259
Brad Bumbalough

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

(code ci-dessus par danburzo @ github)

55
ErichBSchulz

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

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

Fournir un accessoire key au composant enfant permet de s'assurer que chaque fois que la valeur de key change de l'extérieur, ce composant est restitué. Par exemple.,

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

Sur sa performance:

Bien que cela puisse sembler lent, , la différence de performances 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 des mises à jour, car diff est contourné pour ce sous-arbre.

21
Lucia

Il y a aussi composantDidUpdate disponible.

Fonction signatur:

componentDidUpdate(prevProps, prevState, snapshot)

Utilisez-la comme une opportunité d’opérer sur le DOM lorsque le composant a été mis à jour. Ne pas être appelé sur render initiale.

Voir Vous n'avez probablement pas besoin d'un état dérivé Article, qui décrit l'Anti-Motif pour componentDidUpdate et getDerivedStateFromProps. Je l'ai trouvé très utile.

18
armin86er

La nouvelle façon de procéder consiste à utiliser useEffect à la place de composantWillReceiveProps à 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 fonctionnant avec 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 à l'aide de setState, à l'aide de useEffect, nous vérifions les modifications apportées à l'accessoire spécifié et prenons les mesures nécessaires pour mettre à jour l'état en cas de modification de l'accessoire.

4
MMO

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

Effacer l'état lorsque les accessoires changent est un anti-motif

Depuis React 16, composantWillReceiveProps est obsolète. À partir de la documentation de réaction, l’approche recommandée dans ce cas est l’utilisation

  1. Composant entièrement contrôlé: la ParentComponent de la ModalBody sera propriétaire de l'état start_time. Ce n'est pas mon approche préférée dans ce cas puisque 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 réaction: https://codesandbox.io/s/6v1znlxyxn . Vous possédez pleinement l'état start_time de votre ModalBody et utilisez getInitialState comme vous l'avez déjà fait. Pour réinitialiser l’état start_time, il vous suffit de changer la clé de la position ParentComponent
2
Lu Tran

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

1. Définir une clé du parent

Quand une clé change, React créera une nouvelle instance de composant plutôt que de mettre à jour l'instance 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 (le composant est peut-être très coûteux à initialiser)

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

1
Ghominejad

C'est assez clair de leurs docs:

If you used componentWillReceiveProps for re-computing some data only when a prop changes, use a memoization helper instead.

Utilisation: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#what-about-memoization

0
Nhut Dinh Ba

Je pense que l'utilisation des ref est sans danger pour moi, n'ayez 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;
        }
    }
}
0
May Weather VN