I have a JSON
file with several categories, each category has a name with a set of input fields with their own name and value.
How can I use setState
to update the value fields of each onChange
? The categories and fields are rendered using map()
.
I am able to make it work without the nested fields but not with. Appreciate any assistance.
[{
"catName": "Category 1",
"fields": [
{
"name": "field 1",
"amount": "0"
},
{
"name": "field 2",
"amount": "0"
}
]
},
{
"catName": "Category 2",
"fields": [
{
"name": "field 1",
"amount": "0"
},
{
"name": "field 2",
"amount": "0"
}
}]
import React, { Component } from "react";
import Category from "./Category";
import sampleData from "./sampleData";
class Main extends Component {
constructor(props) {
super(props);
this.state = {
list: sampleData
};
}
handleChange = e => {
this.setState({ ??? });
};
render() {
return (
<div>
{this.state.list.map(item => (
<Category
id={item.catName}
name={item.catName}
key={item.catName}
list={item}
handleChange={this.handleChange}
/>
))}
</div>
);
}
}
export default Main;
import React from "react";
import Item from "./Item";
const Category = ({ name, list, handleChange }) => {
return (
<div className="section">
<h3>{name}</h3>
{list.fields.map(item => (
<Item
id={item.name}
name={item.name}
key={item.name}
list={item}
handleChange={handleChange}
/>
))}
</div>
);
};
export default Category;
import React from "react";
const Item = ({ list, handleChange }) => {
return (
<div className="item">
<label className="label">{list.name}</label>
<input
name={list.name}
id={list.name}
className="input"
type="text"
onChange={handleChange}
value={list.amount}
/>
</div>
);
};
export default Item;
Your JSON is invalid. You also forgot to check if list
already contains any data.
Try this:
In your handleChange
method make sure to use correct JSON markup. You forgot the closing ]}
:
this.setState({ list: [{
"catName": "Category 1",
"fields": [
{
"name": "field 1",
"amount": "0"
},
{
"name": "field 2",
"amount": "0"
}
]
},
{
"catName": "Category 2",
"fields": [
{
"name": "field 1",
"amount": "0"
},
{
"name": "field 2",
"amount": "0"
}
]}
]})
Inside the render method of your Main
class check if the list is an array and if its length is bigger than 0. This will prevent any render errors, in case a non array type of value is set.
{Array.isArray(this.state.list) && this.state.list.length < 0 && this.state.list.map(item => (
<Category
id={item.catName}
name={item.catName}
key={item.catName}
list={item}
handleChange={this.handleChange}
/>
))}
Also make sure to set an empty array inside the constructor of your Main Class:
constructor(props) {
super(props);
this.state = {
list: []
};
}
let's start from bottom up
you need to supply to Item.js the id of its parent category by changing is id to id={${name},${item.name}}
. also will be nice to add onClick event to clean the previous data
the category component need to supply is id to the item component
this is the result:
Main.js
import React, { Component } from "react";
import Category from "./Category";
import sampleData from "./sampleData";
class Main extends Component {
constructor(props) {
super(props);
this.state = {
list: sampleData
};
}
createNewData = (mainAccess, property, value) => {
let newData = sampleData;
newData.forEach(category => {
if (category["catName"] === mainAccess) {
debugger;
category["fields"].forEach(item => {
if (item["name"] === property) {
console.log(item["amount"]);
item["amount"] = value;
}
});
}
});
return newData
};
handleChange = e => {
const propertyAccess = e.target.id.split(",");
const newData = this.createNewData(propertyAccess[0],propertyAccess[1],e.target.value)
this.setState({list:newData})
};
render() {
return (
<div>
{this.state.list.map(item => (
<Category
id={item.catName}
name={item.catName}
key={item.catName}
list={item}
handleChange={this.handleChange}
/>
))}
</div>
);
}
}
export default Main;
Item.js
import React from "react";
const Item = ({ list, handleChange ,id}) => {
return (
<div className="item">
<label className="label">{list.name}</label>
<input
name={list.name}
id={id}
className="input"
type="text"
onChange={handleChange}
onClick={e=>e.target.value=""}
value={list.amount}
/>
</div>
);
};
export default Item;
Category.js
import React from "react";
import Item from "./Item";
const Category = ({ name, list, handleChange }) => {
return (
<div className="section">
<h3>{name}</h3>
{list.fields.map(item => (
<Item
id={`${name},${item.name}`}
name={item.name}
key={item.name}
list={item}
handleChange={handleChange}
/>
))}
</div>
);
};
export default Category;
Update your code as follows
import React, { Component } from "react";
import Category from "./Category";
import sampleData from "./sampleData";
class Main extends Component {
constructor(props) {
super(props);
this.state = {
list: sampleData
};
}
handleChange = (e, fieldName, catName) => {
//get list from state
const { list } = this.state
//this returns the related item's index, I assume that all cats have a unique name, otherwise you should use unique values such as IDs
const targetCatIndex = list.findIndex(item => item.catName === catName)
//find related field index
const targetFieldIndex = list[targetCatIndex].fields.findIndex(item => item.name === fieldName)
//update the field and assign to state
list[targetCatIndex].fields[targetFieldIndex].amount = e.target.value
this.setState({ list: list });
};
render() {
return (
<div>
{this.state.list.map(item => (
<Category
id={item.catName}
name={item.catName}
key={item.catName}
list={item}
handleChange={this.handleChange}
/>
))}
</div>
);
}
}
export default Main;
import React from "react";
import Item from "./Item";
const Category = ({ name, list, handleChange }) => {
return (
<div className="section">
<h3>{name}</h3>
{list.fields.map(item => (
<Item
id={item.name}
name={item.name}
key={item.name}
list={item}
// pass field and cat referance with input event
handleChange={(e, fieldName) => handleChange(e, fieldName, name) }
/>
))}
</div>
);
};
export default Category;
import React from "react";
const Item = ({ list, handleChange }) => {
return (
<div className="item">
<label className="label">{list.name}</label>
<input
name={list.name}
id={list.name}
className="input"
type="text"
//pass related field referance here
onChange={(e) => handleChange(e, list.name)}
value={list.amount}
/>
</div>
);
};
export default Item;
And here is the working demo
Pass the category and item index to your handleChange
function. Use those index to update the correct item in the array. Avoid state mutation by not doing
// state mutation
this.state.list[categoryIndex].fields[fieldIndex].amount = e.target.value
handleChange function
handleChange = (e, categoryIndex, itemIndex) => {
const { list } = this.state;
const fields = [...list[categoryIndex].fields.slice(0, itemIndex),
Object.assign({}, list[categoryIndex].fields[itemIndex], { amount: e.target.value }),
...list[categoryIndex].fields.slice(itemIndex + 1)
]
this.setState({
list: [...list.slice(0, categoryIndex),
Object.assign({}, list[categoryIndex], { fields }),
...list.slice(categoryIndex + 1)
]
})
}
Item component, add category and filed index as props.
import React from "react";
const Item = ({ list, handleChange, categoryIndex, itemIndex, value }) => {
return (
<div className="item">
<label className="label">{list.name}</label>
<input
name={list.name}
id={list.name}
className="input"
type="text"
value={value}
onChange={(e) => handleChange(e, categoryIndex, itemIndex)}
/>
</div>
);
};
export default Item;
Category component
import React from "react";
import Item from "./Item";
const Category = ({ name, list, handleChange, categoryIndex }) => {
return (
<div className="section">
<h3>{name}</h3>
{list.fields.map((item, index) => (
<Item
id={item.name}
name={item.name}
key={item.name}
list={item}
categoryIndex={categoryIndex}
itemIndex={index}
value={item.amount}
handleChange={handleChange}
/>
))}
</div>
);
};
export default Category;
DEMO
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.0/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.0/umd/react-dom.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.21.1/babel.min.js"></script> <div id="root"></div> <script type="text/babel"> const Item = ({ list, handleChange, categoryIndex, itemIndex, value }) => { return ( <div className="item"> <label className="label">{list.name}</label> <input name={list.name} id={list.name} className="input" type="text" value={value} onChange={(e) => handleChange(e, categoryIndex, itemIndex)} /> </div> ); }; const Category = ({ name, list, handleChange, categoryIndex }) => { return ( <div className="section"> <h3>{name}</h3> {list.fields.map((item, index) => ( <Item id={item.name} name={item.name} key={item.name} list={item} categoryIndex={categoryIndex} itemIndex={index} value={item.amount} handleChange={handleChange} /> ))} </div> ); }; class App extends React.Component { constructor() { super(); this.state = { name: 'React', show: false, list: [ { "catName": "Category 1", "fields": [ { "name": "field 1", "amount": "0" }, { "name": "field 2", "amount": "0" } ] }, { "catName": "Category 2", "fields": [ { "name": "field 1", "amount": "0" }, { "name": "field 2", "amount": "0" } ] } ] }; } handleChange = (e, categoryIndex, itemIndex) => { const { list } = this.state; const fields = [...list[categoryIndex].fields.slice(0, itemIndex), Object.assign({}, list[categoryIndex].fields[itemIndex], { amount: e.target.value }), ...list[categoryIndex].fields.slice(itemIndex + 1) ] this.setState({ list: [...list.slice(0, categoryIndex), Object.assign({}, list[categoryIndex], { fields }), ...list.slice(categoryIndex + 1) ] }) } show = () => { this.setState({ show: true }) } render() { return ( <div> {this.state.list.map((item, index) => ( <Category id={item.catName} name={item.catName} key={item.catName} categoryIndex={index} list={item} handleChange={this.handleChange} /> ))} <br /> <button onClick={this.show}>Show changes</button> {this.state.show && <pre> {JSON.stringify(this.state.list, null, 4)} </pre> } </div> ); } } ReactDOM.render( <App />, document.getElementById('root') ); </script>
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.