Je crée une application où l'utilisateur peut créer son propre formulaire. Par exemple. spécifiez le nom du champ et les détails des autres colonnes à inclure.
Le composant est disponible en tant que JSFiddle ici .
Mon état initial ressemble à ceci:
var DynamicForm = React.createClass({
getInitialState: function() {
var items = {};
items[1] = { name: 'field 1', populate_at: 'web_start',
same_as: 'customer_name',
autocomplete_from: 'customer_name', title: '' };
items[2] = { name: 'field 2', populate_at: 'web_end',
same_as: 'user_name',
autocomplete_from: 'user_name', title: '' };
return { items };
},
render: function() {
var _this = this;
return (
<div>
{ Object.keys(this.state.items).map(function (key) {
var item = _this.state.items[key];
return (
<div>
<PopulateAtCheckboxes this={this}
checked={item.populate_at} id={key}
populate_at={data.populate_at} />
</div>
);
}, this)}
<button onClick={this.newFieldEntry}>Create a new field</button>
<button onClick={this.saveAndContinue}>Save and Continue</button>
</div>
);
}
Je souhaite mettre à jour l'état lorsque l'utilisateur modifie une valeur, mais j'ai du mal à cibler le bon objet:
var PopulateAtCheckboxes = React.createClass({
handleChange: function (e) {
item = this.state.items[1];
item.name = 'newName';
items[1] = item;
this.setState({items: items});
},
render: function() {
var populateAtCheckbox = this.props.populate_at.map(function(value) {
return (
<label for={value}>
<input type="radio" name={'populate_at'+this.props.id} value={value}
onChange={this.handleChange} checked={this.props.checked == value}
ref="populate-at"/>
{value}
</label>
);
}, this);
return (
<div className="populate-at-checkboxes">
{populateAtCheckbox}
</div>
);
}
});
Comment dois-je créer this.setState
pour le mettre à jour items[1].name
?
Vous pouvez utiliser l'auxiliaire update
immutability helper }:
this.setState({
items: update(this.state.items, {1: {name: {$set: 'updated field name'}}})
})
Ou bien, si vous ne souhaitez pas détecter les modifications apportées à cet élément dans une méthode de cycle de vie shouldComponentUpdate()
à l'aide de ===
, vous pouvez directement modifier l'état et forcer le composant à effectuer un nouveau rendu. C'est en fait la même chose que la réponse de @limelights car il tire un objet hors de son état et l’édite.
this.state.items[1].name = 'updated field name'
this.forceUpdate()
Ajout post-édition:
Consultez la communication de composant simple leçon de react-training pour un exemple sur la manière de passer une fonction de rappel d'un parent détenteur d'état à un composant enfant devant déclencher un changement d'état .
Fausse route!
handleChange = (e) => {
const { items } = this.state;
items[1].name = e.target.value;
// update state
this.setState({
items,
});
};
Comme l'ont souligné beaucoup de meilleurs développeurs dans les commentaires: la mutation de l'état est fausse!
Il m'a fallu un peu de temps pour comprendre cela. Au-dessus, ça marche, mais ça enlève le pouvoir de React. Par exemple, componentDidUpdate
ne verra pas cela comme une mise à jour car elle est modifiée directement.
Alors la bonne façon serait:
handleChange = (e) => {
this.setState(prevState => ({
items: {
...prevState.items,
[this.state.items[1].name]: e.target.value
},
}));
};
Comme il y a beaucoup de désinformation dans ce fil, voici comment vous pouvez le faire sans bibliothèque auxiliaire:
handleChange: function (e) {
// 1. Make a shallow copy of the items
let items = [...this.state.items];
// 2. Make a shallow copy of the item you want to mutate
let item = {...items[1]};
// 3. Replace the property you're intested in
item.name = 'newName';
// 4. Put it back into our array. N.B. we *are* mutating the array here, but that's why we made a copy first
items[1] = item;
// 5. Set the state to our new copy
this.setState({items});
},
Vous pouvez combiner les étapes 2 et 3 si vous le souhaitez:
let item = {
...items[1],
name: 'newName'
}
Ou vous pouvez tout faire en une seule ligne:
this.setState(({items}) => ({
items: [
...items.slice(0,1),
{
...items[1],
name: 'newName',
},
...items.slice(2)
]
}));
Note: J'ai fait items
un tableau. OP a utilisé un objet. Cependant, les concepts sont les mêmes.
Vous pouvez voir ce qui se passe dans votre terminal/console:
❯ node
> items = [{name:'foo'},{name:'bar'},{name:'baz'}]
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ]
> clone = [...items]
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ]
> item1 = {...clone[1]}
{ name: 'bar' }
> item1.name = 'bacon'
'bacon'
> clone[1] = item1
{ name: 'bacon' }
> clone
[ { name: 'foo' }, { name: 'bacon' }, { name: 'baz' } ]
> items
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ] // good! we didn't mutate `items`
> items === clone
false // these are different objects
> items[0] === clone[0]
true // we don't need to clone items 0 and 2 because we're not mutating them (efficiency gains!)
> items[1] === clone[1]
false // this guy we copied
Tout d’abord, récupérez l’article que vous voulez, changez ce que vous voulez sur cet objet et réglez-le sur l’état . La manière dont vous utilisez l’état en passant seulement un objet dans getInitialState
serait beaucoup plus simple si vous utilisiez un objet.
handleChange: function (e) {
item = this.state.items[1];
item.name = 'newName';
items[1] = item;
this.setState({items: items});
}
Ne mute pas l'état en place. Cela peut provoquer des résultats inattendus. J'ai appris ma leçon! Travaillez toujours avec une copie/un clone, Object.assign()
en est un bon:
item = Object.assign({}, this.state.items[1], {name: 'newName'});
items[1] = item;
this.setState({items: items});
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
J'ai eu le même problème. Voici une solution simple qui fonctionne!
const newItems = [...this.state.items];
newItems[item] = value;
this.setState({ items:newItems });
Selon la documentation de React sur setState , utiliser Object.assign
comme suggéré par d’autres réponses n’est pas idéal. En raison de la nature du comportement asynchrone de setState
, les appels ultérieurs utilisant cette technique peuvent remplacer les appels précédents, entraînant des résultats indésirables.
Au lieu de cela, les documents React recommandent d'utiliser le formulaire de mise à jour de setState
qui opère sur l'état précédent. Gardez à l'esprit que lors de la mise à jour d'un tableau ou d'un objet, vous devez renvoyer un nouveau tableau ou objet}, car React nous oblige à préserver l'immuabilité de l'état. En utilisant l'opérateur spread de la syntaxe ES6 pour copier peu profond un tableau, la création ou la mise à jour d'une propriété d'un objet à un index donné du tableau ressemblerait à ceci:
this.setState(prevState => {
const newItems = [...prevState.items];
newItems[index].name = newName;
return {items: newItems};
})
C'est vraiment simple.
Tout d’abord, extrayez l’objet items entier de l’état, mettez à jour la partie de l’objet items comme vous le souhaitez, puis remettez l’objet items en état avec setState.
handleChange: function (e) {
items = Object.assign(this.state.items); // Pull the entire items object out. Using object.assign is a good idea for objects.
items[1].name = 'newName'; // update the items object as needed
this.setState({ items }); // Put back in state
}
Mutation libre:
// given a state
state = {items: [{name: 'Fred', value: 1}, {name: 'Wilma', value: 2}]}
// This will work without mutation as it clones the modified item in the map:
this.state.items
.map(item => item.name === 'Fred' ? {...item, ...{value: 3}} : item)
this.setState(newItems)
Essayez ceci cela fonctionnera certainement, un autre cas j'ai essayé mais n'ai pas fonctionné
import _ from 'lodash';
this.state.var_name = _.assign(this.state.var_name, {
obj_prop: 'changed_value',
});
ou si vous avez une liste générée dynamiquement et que vous ne connaissez pas l'index mais avez juste la clé ou l'identifiant:
let ItemsCopy = []
let x = this.state.Items.map((entry) =>{
if(entry.id == 'theIDYoureLookingFor')
{
entry.PropertyToChange = 'NewProperty'
}
ItemsCopy.Push(entry)
})
this.setState({Items:ItemsCopy});
Utilisez l'événement sur handleChange
pour identifier l'élément qui a été modifié, puis mettez-le à jour. Pour cela, vous devrez peut-être modifier une propriété pour l'identifier et la mettre à jour.
Voir le violon https://jsfiddle.net/69z2wepo/6164/
Pourquoi ne pas créer un autre composant (pour un objet devant être inséré dans le tableau) et passer les éléments suivants comme accessoires?
<SubObjectForm setData={this.setSubObjectData} objectIndex={index}/>
Ici, {index} peut être passé en fonction de la position où ce SubObjectForm est utilisé.
et setSubObjectData peuvent être quelque chose comme ceci.
setSubObjectData: function(index, data){
var arrayFromParentObject= <retrieve from props or state>;
var objectInArray= arrayFromParentObject.array[index];
arrayFromParentObject.array[index] = Object.assign(objectInArray, data);
}
Dans SubObjectForm, this.props.setData peut être appelé en cas de modification de données, comme indiqué ci-dessous.
<input type="text" name="name" onChange={(e) => this.props.setData(this.props.objectIndex,{name: e.target.value})}/>
Comme aucune des options ci-dessus n’était idéale pour moi, j’ai fini par utiliser map:
this.setState({items: this.state.items.map((item,idx)=> idx!==1 ?item :{...item,name:'new_name'}) })
Je déplacerais le changement de descripteur de fonction et ajouterais un paramètre d'index
handleChange: function (index) {
var items = this.state.items;
items[index].name = 'newName';
this.setState({items: items});
},
au composant de formulaire dynamique et transmettez-le au composant PopulateAtCheckboxes en tant que prop. Lorsque vous passez en boucle sur vos éléments, vous pouvez inclure un compteur supplémentaire (appelé index dans le code ci-dessous) à transmettre au changement de traitement, comme indiqué ci-dessous.
{ Object.keys(this.state.items).map(function (key, index) {
var item = _this.state.items[key];
var boundHandleChange = _this.handleChange.bind(_this, index);
return (
<div>
<PopulateAtCheckboxes this={this}
checked={item.populate_at} id={key}
handleChange={boundHandleChange}
populate_at={data.populate_at} />
</div>
);
}, this)}
Enfin, vous pouvez appeler votre auditeur de modification comme indiqué ci-dessous
<input type="radio" name={'populate_at'+this.props.id} value={value} onChange={this.props.handleChange} checked={this.props.checked == value} ref="populate-at"/>
La suite du code a été facile sur mon cerveau terne. Supprimer l'objet et le remplacer par celui mis à jour
var udpateditem = this.state.items.find(function(item) {
return item.name == "field_1" });
udpateditem.name= "New updated name"
this.setState(prevState => ({
items:prevState.dl_name_template.filter(function(item) {
return item.name !== "field_1"}).concat(udpateditem)
}));
Si vous ne devez modifier qu'une partie de la Array
, .__, vous avez un composant de réaction avec l'état défini sur.
state = {items: [{name: 'red-one', value: 100}, {name: 'green-one', value: 999}]}
Il est préférable de mettre à jour le red-one
dans la Array
comme suit:
const itemIndex = this.state.items.findIndex(i=> i.name === 'red-one');
const newItems = [
this.state.items.slice(0, itemIndex),
{name: 'red-one', value: 666},
this.state.items.slice(itemIndex)
]
this.setState(newItems)
Essayez avec le code:
this.state.items[1] = 'new value';
var cloneObj = Object.assign({}, this.state.items);
this.setState({items: cloneObj });