How to iterate array of objects with properties in ReactJS? - reactjs

I have a constructor in my main component:
class App extends Component {
constructor(props){
super(props);
this.state = {
items: []
}
};
render() {
return (
<div className="App">
<ItemList items={this.state.items}/>
<AddItemForm items={this.state.items}/>
</div>
);
}
}
In component AddItemForm I'm adding to array items objects with properties "item_name" that is string and "comment" with data type object. View of component:
class AddItemForm extends React.Component {
constructor(props) {
super(props);
this.state = {
item:{}
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({item:
{
item_name: event.target.value,
comment:{}
}
});
}
handleSubmit(event) {
event.preventDefault();
this.props.items.push(this.state.item);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
<input type="text" item_name={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
export default AddItemForm;
How can I iterate this array to get all item_name values of every object and display them as list in my ItemList component?

This should help.
class App extends Component {
constructor(props){
super(props);
this.state = {
items: []
}
};
addItemToItemsList = (item) => {
const {items=[]} = this.state;
items.push(item);
this.setState({
items : items
});
}
render() {
return (
<div className="App">
<ItemList items={this.state.items}/>
<AddItemForm
items={this.state.items}
addItemToItemsList={this.addItemToItemsList}
/>
</div>
);
}
}
class ItemList extends React.Component {
render () {
const {items} = this.props;
return (
<div>
{items.map((item, index) => {
return (
<div key={index}>item.item_name</div>
)
})}
</div>
);
}
}
class AddItemForm extends React.Component {
constructor(props) {
super(props);
this.state = {
item: {
item_name : '',
comment:{}
}
};
}
handleChange = (event) => {
const new_item = Object.assign({}, this.state.item, {item_name: event.target.value});
this.setState({
item: new_item
});
}
handleSubmit = (event) => {
event.preventDefault();
this.props.addItemToItemsList(this.state.item);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
<input type="text" item_name={this.state.item.item_name} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
export default AddItemForm;

I think, you have an error inside AddItemForm, you should pass onSubmit function from App to AddItemForm and change items through this function:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
items: []
}
this.handleSubmit = this.handleSubmit.bind(this);
};
handleSubmit(value){
this.setState({
items: this.state.items.concat(value)
})
}
render() {
return (
<div className="App">
<ItemList items={this.state.items} />
<AddItemForm
onSubmit={this.handleSubmit}
items={this.state.items} />
</div>
);
}
}
About main question, one of the way to solve this problem
const ItemList = ({items}) => (
<div>
{items.map( (item, index)=> (
<div key={index}>{item.item_name}</div>
))}
</div>
);
full working example here: https://codesandbox.io/s/7k624nz94q

You can't add to the array directly. You need to pass a callback that will add to the array in your parent component's state. This is a very common pattern when using react.
Here is a skeleton of what you need to do:
In your parent component, you don't need to pass the whole list to your AddItemForm component, just a addItem callback to your child component:
class App extends Component {
constructor(props){
super(props);
this.state = {
items: []
}
this.addItemToList = this.addItemToList.bind(this);
};
render() {
return (
<div className="App">
<ItemList items={this.state.items}/>
<AddItemForm addItemToList={this.addItemToList}/>
</div>
);
}
addItemToList(newValue) {
// Here you add the item to your state
// Always treat your state as immutable, so create a copy then add the item, then set your new State
const newArray = this.state.items.slice(); // clone
newArray .push(newValue); // Add value
this.setState({items: newArray}); // Set the new state
}
}
More info on how to add items to an array in the state here: React.js - What is the best way to add a value to an array in state
Then you use that callback in your child component:
handleSubmit(event) {
event.preventDefault();
// this.props.items.push(this.state.item);
// Here don't mutate the props, instead call the callback to add the item to your parent's component's state
this.props.addItemToList(this.state.item);
}
To display a list of items, you need to use the map function: https://reactjs.org/docs/lists-and-keys.html#rendering-multiple-components

Related

ReactJS: pass data through 3 components

I'm trying to pass data through 3 components within the hierarchy (Parent-Child-Child2):
Parent-class:
class Parent extends React.Component {
constructor(props) {
super(props);
this.handleOperation = this.handleOperation.bind(this);
}
handleOperation = (value) => {
// some actions
};
render() {
return (
<div className={styles}>
<Child valueChild={this.handleOperation()}/>
</div>
);
}
}
Child1:
class Child extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange =(value) => {
this.props.valueChild(value);
};
render() {
return (
<div>
<Child2 childValue2 = {this.handleChange} />
</div>
);
}
}
Child2:
class Child2 extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange =(event) => {
this.props.childValue2(event.target.value);
};
render() {
return (
<div>
<input type="button" onClick={this.handleChange} value="defaultValue" />
</div>
);
}
}
Code compiling successfully, but when I click on input-field (to pass value to Parent), I receive the following error:

async function passed as props, event handler could not call this function

I'm passing a async function from App to Header to Form. I cant seems to call that function with submit event handler even with the use of arrow function.
I've tried fat arrow function. The only working way is to move the function down to Form which it work but not ideal as I need the data to be stored in App state. when I console.log(result) I get undefined
class Form extends Component {
constructor(props){
super(props);
this.state = {value: ''};
}
handleChange=(e)=> {
this.setState({value: e.target.value});
}
handleSubmit= (e)=>{
e.preventDefault();
const value = this.state.value;
this.props.getResult(value)
}
render(){
return (
<form className="search"onSubmit={this.handleSubmit}>
<input type="text" className="search__field" placeholder="Search over 1,000,000 recipes..." value={this.state.value} onChange={this.handleChange}/>
<button className="btn search__btn"></button>
</form>
);
}
}
App component
class App extends Component {
constructor(props) {
super(props);
this.state = {
search:[],
}
}
getResult = async (query) =>{
try {
const key = 'key';
const res = await axios(`https://www.food2fork.com/api/search?key=${key}&q=${query}`);
const result = res.data.recipes;
console.log(result)
// if(result) this.setState({search: result});
}
catch (err) {
alert(err);
}
}
render() {
return (
<div className="App">
<div className="container">
<Header getResult={this.getResult}/>
</div>
</div>
);
}
}
Header
class Header extends Component {
constructor(props) {
super(props);
this.getResult = this.props.getResult;
}
render(){
return (
<div className="header">
<Logo />
<Form getResult={this.getResult}/>
<Like />
</div>
)
}
}

React component: Simulate standard HTML <input/> API

I want to make a component with an API like any standard input element, meaning I want to use it like this: <CustomInput value={this.state.custom_input_state} onChange={this.handleChange} />
Here is what I have so far, but I have no idea how to
Make the custom components value changeable from the parent component
after it has been constructed
Make the parent's onChange handler function recieve a change event when the
custom component's value changes
Here is my test setup:
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
foo: 0
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.increment = this.increment.bind(this);
}
handleChange(event) {
this.setState({[event.target.name]: event.target.value});
}
handleSubmit(event) {
alert(this.state.foo);
event.preventDefault();
}
increment() {
this.setState({foo: this.state.foo + 1});
}
render() {
return(
<form onSubmit={this.handleSubmit}>
<div onClick={this.increment}>Increment from parent</div>
<CustomInput name="foo" value={this.state.foo} onChange={this.handleChange}/>
<input type="submit" value="Submit" />
</form>
)
}
}
class CustomInput extends React.Component {
constructor(props) {
super(props);
this.state = {
value: this.props.value,
};
this.increment = this.increment.bind(this);
}
increment() {
this.setState({value: this.state.value + 1});
}
render() {
return(
<React.Fragment>
<div onClick={this.increment}>Increment self</div>
<input name={this.props.name} value={this.state.value}/>
</React.Fragment>
);
}
}
You have to pass all the CustomInput props to the input element. In CustomInput component actually it not recieving the onChange event.
Pass the prop onChange event to input element
Form Component
class Form extends Component {
constructor() {
super();
this.state = {
foo: 'React'
};
}
handleChange = (event) => {
this.setState({
[event.target.name]:event.target.value
})
}
render() {
return (
<div>
<form>
<Custominput name="foo" onChange={this.handleChange} value={this.state.foo} />
</form>
{this.state.foo}
</div>
);
}
}
CustomInput Component
export default class CustomInput extends React.Component{
render(){
return(
<input {...this.props} />
)
}
}
demo link

passing state of child component

So I have a component "itemSelection" and inside of it I map through an api response like this
<div className="row">
{this.state.items.map(i => <Item name={i.name} quantity={i.quantity} />)}
</div>
Here the state of "Item" component
constructor(props){
super(props);
this.state = {
visible: false,
selected: false,
}
}
How could I pass the state of "Item" component to "itemSelection" component?
Sending data back up to your parent component should be done by using props.
Fairly common question, see this post for the long answer.
As according to me, If I understood your question you want to call the state of the child component to the parent component.
//Child.js
import s from './Child.css';
class Child extends Component {
getAlert() {
alert('clicked');
}
render() {
return (
<h1 ref="hello">Hello</h1>
);
}
}
export default withStyles(s)(Child);
//Parent.js
class Parent extends Component {
render() {
onClick() {
this.refs.child.getAlert()
}
return (
<div>
<Child ref="child" />
<button onClick={this.onClick.bind(this)}>Click</button>
</div>
);
}
}
Also, you can get the code reference from the link: https://github.com/kriasoft/react-starter-kit/issues/909
This a little tricky but Maybe, its help you solving your problem.
//Parent.js
class Parent extends Component {
component(props) {
super(props)
this.state = {
test: 'abc'
}
}
ParentFunction = (value) => {
this.state.test = value;
this.setState(this.state);
}
render() {
return (
<div>
<Child
test={this.state.test}
ParentFunction={this.ParentFunction}
/>
</div>
);
}
}
//Child.js
import s from './Child.css';
class Child extends Component {
component(props) {
super(props)
this.state = {
test: props.test
}
}
handleChange = () => {
this.state.test = event.target.value;
this.setState(this.state);
this.handleOnSave()
}
handleOnSave = () => {
this.props.ParentFunction(this.state.test);
}
render() {
return (
<div>
<input type="text" onChange={this.handleChange} />
</div>
);
}
}
export default withStyles(s)(Child);

React js - Uncaught TypeError: Cannot read property of undefined

I have a problem (
There are my components:
class Main extends Component {
constructor(props) {
super(props);
this.state = {
total: 0
};
this.totalFuns = this.totalFuns.bind(this);
}
totalFuns(event){
this.setState({total: event})
}
render() {
return (
<main>
<Item data_items={data} data_cnt={this.totalFuns} />
</main>
);
}
}
export default Main;
then Item component:
class Item extends Component {
constructor(props){
super(props);
this.skuChange = this.skuChange.bind(this);
}
skuChange(event) {
this.props.data_cnt(event)
}
render() {
return (
<section className="item" data-index={this.props.data_index}>
<Select values={this.props.data_items} onChange={this.skuChange}/>
</section>
);
}
}
export default Item;
then Select component
class Select extends Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.props.onChange(event.target.value);
this.setState({value: event.target.label});
}
render() {
var options = this.props.values;
var options_list = options.map(function(obj,i){
return (
<option value={i} key={i} label={obj.label} />
)
});
return (
<select value={this.state.value} onChange={this.handleChange}>
{options_list}
</select>
);
}
}
export default Select;
In console show: Uncaught TypeError: Cannot read property 'totalFuns' of undefined. Without this fun my app work correct.. (
Why?
Your function is actually called totalFuns but you have totalFun.
So this line:
<Item data_items={data} data_cnt={this.totalFun} />
should be
<Item data_items={data} data_cnt={this.totalFuns} />
In your Select component, the state value was initially undefined and hence you were getting error. Also, the options can be selected based on value instead of label. See the working snippet below. Also I do not see data being present in the Main component
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
total: 0
};
this.totalFuns = this.totalFuns.bind(this);
}
totalFuns(event){
console.log(event);
this.setState({total: event})
}
render() {
var data = [{label: 1}, {label: 2}]
return (
<main>
<Item data_items={data} data_cnt={this.totalFuns} />
</main>
);
}
}
class Item extends React.Component {
constructor(props){
super(props);
this.skuChange = this.skuChange.bind(this);
}
skuChange(event) {
this.props.data_cnt(event)
}
render() {
return (
<section className="item" data-index={this.props.data_index}>
<Select values={this.props.data_items} onChange={this.skuChange}/>
</section>
);
}
}
class Select extends React.Component {
constructor(props) {
super(props);
this.state = {
value: ''
}
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.props.onChange(event.target.value);
this.setState({value: event.target.value});
}
render() {
var options = this.props.values;
var options_list = options.map(function(obj,i){
return (
<option value={obj.label} key={i} label={obj.label} />
)
});
return (
<select value={this.state.value} onChange={this.handleChange}>
{options_list}
</select>
);
}
}
ReactDOM.render(<Main/>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script>
<div id="app"></div>

Resources