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