Javascript object id field not being passed to event handler - javascript

I have a react app containing two components, Counters and Counter, where the state object of the Counters component contains an array of objects with each representing a Counter.
In the actual jsx code for the Counters component, the items in the counters array is being rendered with each containing a delete button where each individual counter can be deleted.Right now, I have an arrow function to handle the delete which is set as an property in the Counter tags being rendered. Within the Counter component, there is an onCLick event in the delete button which takes as a paramerter the id of the Counter that was clicked.
For some reason, the delete is not working and when I console log the id of the Counter that has been clicked on, undefined is printed. What could be causing the id property not to be read from the Counter component?
The relevant code is below:
Counter component:
class Counter extends Component {
state = {
value: this.props.value
};
render() {
console.log(this.props);
return (
<div>
{this.props.children}
<span className={this.getBadgeClasses()}>{this.formatCount()}</span>
<button
onClick={() => this.handleIncrement({ id: 1 })}
className="btn btn-sercondary btn-sm"
>
Increment
</button>
<button
onClick={() => this.props.onDelete(this.props.id)}
className="btn btn-danger btn-sm m-2"
>
Delete
</button>
</div>
);
}
Counters Component:
import Counter from "./counter";
class Counters extends Component {
state = {
counters: [
{ id: 1, value: 4 },
{ id: 2, value: 0 },
{ id: 3, value: 0 },
{ id: 4, value: 0 }
]
};
handleDelete = counterId => {
console.log("Event Handler Called", counterId);
const counters = this.state.counters.filter(c => c.id !== counterId);
this.setState({ counters });
};
render() {
return (
<div>
{this.state.counters.map(counter => (
<Counter
key={counter.id}
onDelete={this.handleDelete}
value={counter.value}
/>
))}
</div>
);
}
}

You need to pass prop id to Couter in the render function of component Couters since the button need it <button onClick={() => this.props.onDelete(this.props.id)} ;
See here
<Counter
id={counter.id}
key={counter.id}
onDelete={this.handleDelete}
value={counter.value}
/>
Alternatively, you can do this
<Counter
key={counter.id}
onDelete={() => this.handleDelete(counter.id)}
value={counter.value}
/>

You have a typo.
You should use this.props.key inside the delete method in Component class instead of this.props.id.

Related

How to show objects from array one by one , in single component

I'm trying to build a component who going to have multiple objects from the array.
I want to show them one by one.
Let's assume, I have 3 objects inside the array. Then first object-related content should be shown first until the user opts to continue or skip for the next object until all 3 objects have been shown. How can I do this inside the One page?
For example, this is a minimal Code that how I'm going to make a component, I want to handle the Data like that each object should be shown independently and move to next on user input. Whether the user wants to skip or continue, the next object should be shown on the same page.
import { Fragment } from 'react'
import Data from 'Data'
const Main = () => {
const data = [
{ id: 1, more: '...' },
{ id: 2, more: '...' },
{ id: 3, more: '...' }
]
const submitHandler = () => {
// some action
}
return (
<Fragment>
<Card style={{ minHeight: '40rem' }}>
<Card.Body>{data ? data.map((el) => <div key={el.id} >
<Data obj={el} /> // Passing to child
</div>) : null}
</Card.Body>
<Card.Footer>
<Button variant="outline-danger" onClick={submitHandler} className="mx-1">
Skip
</Button>
<Button variant="primary" onClick={submitHandler}>
Continue
</Button>
</Card.Footer>
</Card>
</Fragment>
)
}
export default Main
Edit:
#jasonmzx below suggested some solution but it's giving type error. Can anybody fix this here , CodeSandBox
Here you could use the state to track which index the user is currently on and render the output based on your array
import { Fragment, useState } from 'react';
import Data from 'Data';
const Main = () => {
const [index,setIndex] = React.useState(0); // Starting from the beginning
const data = [
{ id: 1, more: '...' },
{ id: 2, more: '...' },
{ id: 3, more: '...' }
]
// I'm making this a function for cleanliness
// This renders the array at the index of the state
const showDat = (data, index) => {
return
<p>{data[index].more}</p>
}
const submitHandler = () => {
// Skip or cont: update state!
setIndex(index+1);
}
return (
<Fragment>
<Card style={{ minHeight: '40rem' }}>
<Card.Body>{data ? data.map((el) => <div key={el.id} >
<Data obj={el} /> // Passing to child
</div>) : null}
</Card.Body>
<Card.Footer>
<Button variant="outline-danger" onClick={submitHandler} className="mx-1">
Skip
</Button>
<Button variant="primary" onClick={submitHandler}>
Continue
</Button>
{showDat(data, index)}
</Card.Footer>
</Card>
</Fragment>
)
}
export default Main
Basically here is what will happen (when you code the submitHandler); the submitHandler updates the state, the state adds 1 onto index, and since the state has updated, a re-render happens, meaning the showDat() function being called within the Main component's render is being called again with the new index, to display the next index in ur array

React updating elements from one file to other

Hello Guys I am a newbie and trying to learn React. I want to access the state of counters.js from counter.js in the delete button. I tried to print but the output is giving as undefined. The expected output is the button id clicked. Please somebody help. and can someone suggest me a good course to learn React and front-end development?
counter.js
class Counter extends Component {
state = {
value: this.props.value
}
handleIncrement = () => {
this.setState({
value: this.state.value + 1
})
}
render() {
return (<div>
{this.props.children}
<span className={this.getBadgeClasses()}>{this.formatCount()}</span>
<button onClick={this.handleIncrement} className='btn btn-secondary btn-sm'>Increment</button>
<button onClick={() => this.props.onDelete(this.props.id)} className="btn btn-danger btn-sm m-2">Delete</button>
</div>);
}
formatCount() {
const {value} = this.state;
return value === 0
? "Zero"
: value;
}
getBadgeClasses() {
let classes = "badge m-2 badge-";
classes += this.state.value === 0
? "warning"
: "primary";
return classes;
}
}
export default Counter;
Counters.js
import React, {Component} from 'react';
import Counter from './counter';
class Counters extends Component {
state = {
counters: [
{
id: 1,
value: 4
}, {
id: 2,
value: 0
}, {
id: 3,
value: 0
}, {
id: 4,
value: 0
}
]
};
handleDelete = (counterId) => {
console.log('Event Handler Called', counterId);
}
render() {
return (<div>
{this.state.counters.map(counter => (<Counter key={counter.id} onDelete={this.handleDelete} value={counter.value}/>))}
</div>)
}
}
export default Counters;
console
,
change word key to any other letter) and it should works fine
{this.state.counters.map(counter => (<Counter key={counter.id}
to
{this.state.counters.map(counter => (<Counter a={counter.id}
and this line also
<button onClick={() => this.props.onDelete(this.props.key)}
to
<button onClick={() => this.props.onDelete(this.props.a)}
also you can add a key in this way
(counter, i) => (<Counter key={i} a={counter.id}
here is your working solution in two files
Counters.js
export default class Counters extends Component {
constructor(props) {
super(props)
this.state = {
counters: [
{
id: 1,
value: 4
}, {
id: 2,
value: 0
}, {
id: 3,
value: 0
}, {
id: 4,
value: 0
}
]
};
this.handleDelete = this.handleDelete.bind(this)
}
handleDelete = (counterId) => {
console.log(counterId)
console.log('Event Handler Called', counterId);
}
render() {
return (<div>
{this.state.counters.map(counter => (<Counter a={counter.id} onDelete={this.handleDelete} value={counter.value}/>))}
</div>)
}
}
counter.js
import React from 'react'
class Counter extends React.Component {
constructor(props) {
super(props)
this.state = {
value: this.props.value
}
this.getBadgeClasses = this.getBadgeClasses.bind(this)
this.formatCount = this.formatCount.bind(this)
}
handleIncrement = () => {
this.setState({
value: this.state.value + 1
})
}
render() {
return (<div>
{this.props.children}
<span className={this.getBadgeClasses()}>{this.formatCount()}</span>
<button onClick={this.handleIncrement} className='btn btn-secondary btn-sm'>Increment</button>
<button onClick={() => this.props.onDelete(this.props.a)} className="btn btn-danger btn-sm m-2">Delete</button>
</div>);
}
formatCount() {
const {value} = this.state;
return value === 0
? "Zero"
: value;
}
getBadgeClasses() {
let classes = "badge m-2 badge-";
classes += this.state.value === 0
? "warning"
: "primary";
return classes;
}
}
export default Counter;
Please refer the following link :
Passing function to components
and don't forget to bind your function to component instance.
Answer to second question:
If you have github student account (for free access), checkout Frontend Masters. It is an excellent resource for frontend development.
You cannot access a component’s state from outside the component. As it’s stated in the official React documentation:
State is similar to props, but it is private and fully controlled by
the component.
Basically there are two approaches for “exchanging” the state between components:
From parent components to child components with props: Just as you did with the value property of your Counter component
From child components to parent components with event handlers:
If certain actions should trigger a state change of a parent’s component you need to inform the parent about the action with an event handler. You already did that with your onDelete handler.
These are the only two mechanisms in React to "exchange" the state. In your case I suggest to remove the state of your Counter component and to use the value property instead. Additionally you need to add an onIncrement handler (just as you did with onDelete) and move the handleIncrement function to your Counters component. Observe that you need to pass the counter’s id to the handleIncrement function now (since the Counters component does not know which child component triggered the event).
If you apply these changes, the Counter component is stateless and the state of all counters is controlled by the Counters component. Nevertheless since you pass the state via the value property it still gets updated each time the state changed.
For diving into react I suggest to do the React tutorial for beginners (https://reactjs.org/tutorial/tutorial.html) – your issue is addressed here throughout the tutorial (look for “lifting up the state”) and afterwards you could continue with the official documentation (https://reactjs.org/docs/hello-world.html).
You are passing only key, onDelete and value props to component.
{this.state.counters.map(counter => (<Counter key={counter.id} onDelete={this.handleDelete} value={counter.value}/>))}
And you are trying to access this.props.id on delete button click inside counter.js file.
<button onClick={() => this.props.onDelete(this.props.id)} className="btn btn-danger btn-sm m-2">Delete</button>
you should use this.props.key on delete button click instead of this.props.id. The correct delete button code will be like
<button onClick={() => this.props.onDelete(this.props.key)} className="btn btn-danger btn-sm m-2">Delete</button>
You can go through React official documents for better learning of React.
The reason you are getting undefined is because you are not passing id prop for Counter component
{this.state.counters.map(counter => (<Counter key={counter.id} onDelete={this.handleDelete} value={counter.value}/>))}
So passing id prop will work as your expectation:
{this.state.counters.map(counter => (<Counter id={counter.id} key={counter.id} onDelete={this.handleDelete} value={counter.value}/>))}
This is one of the reasons we need some type check tools like PropTypes or Typescript so this kind of mistake can be avoided at compile time.
The best resource for learning React is actually React official documentation
https://reactjs.org/docs/getting-started.html
Once you can digest this already then next step can subscribe Medium to get a lot of good reads so you can have deeper understandings from many aspects and use cases.

React.js - How to execute function in parent with arguments from child

The question is probably rather unclear, but i did not how to formulate it, maybe that was the reason why i was not able to find solution to this puzzle i have. anyway, here is an example of what i want to accomplish:
<Calendar
tileContent={({ activeStartDate, date, view }) =>
this.renderGames(date, view)
}
/>
This is an example from npm package react-calendar, but i am sure you know what i mean. The param tileContent gets passed function that already has destructured object, and then i run my own function with data i get from that function.
I was thinking that this was done by executing function in child where i would pass an object (or single param, i just use object as an example).
I think what you're looking for are Render Props, not just executing function in parent with args (even though render props do this as well). It would appear your example is using Render Props specifically.
There are some good examples online of using render props in React, also referred to as "Wrapper Components", etc..
An example could be something like:
const { render } = ReactDOM;
class CounterWrapper extends React.Component {
state = {
count: 0
};
increment = () => {
const { count } = this.state;
return this.setState({ count: count + 1 });
};
decrement = () => {
const { count } = this.state;
return this.setState({ count: count - 1 });
};
render() {
const { count } = this.state;
return (
<React.Fragment>
{this.props.wrapperContent({
increment: this.increment,
decrement: this.decrement,
count
})}
</React.Fragment>
);
}
}
class App extends React.Component {
renderApp = (cnt, inc, dec) => {
return (
<div>
<h1>Render Props Counter Example</h1>
<div>
<p>{cnt}</p>
<button type="button" onClick={() => inc()}>
Increment
</button>
<button type="button" onClick={() => dec()}>
Decrement
</button>
</div>
</div>
)
};
render() {
return (
<CounterWrapper
wrapperContent={({ count, increment, decrement }) =>
this.renderApp(count, increment, decrement)
}
/>
);
}
}
render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
It sounds like you want to execute a function that's in the parent component, from a child component with arguments passed from the child.
Here is an example:
const ParentComponent = () => {
const handleClick = (args) => {
console.log(args)
}
return (
<div>
<ChildComponent onClick={handleClick} />
</div>
)
}
const ChildComponent = ({onClick}) => {
const val = 5;
return (
<div>
<button onClick={() => handleClick(val)} name="Click">Click Me</button>
</div>
)
}
This hsould render the child component which is just a button, with an event handler that is sent from the parent. When you click the button, you should get a console log of 5, which is coming from the parent. This is how you would propgate values from the child, up to the parent.

Got all button updated in reactjs onclick event

A single click on a button updates all the buttons but I want to change the state of that particular clicked button. Please check the image links below and the code.
import React from 'react';
import './MenuCard.css';
class MenuCard extends React.Component {
constructor(props) {
super(props);
this.state = {
showButton: false,
hideButton: true,
aValue: 1,
breads: [],
category: [],
ids: 1,
btnVal: 'Add'
};
}
onKeyCheck = (e) => {
this.state.breads.map(filt => {
if (filt.id === e.target.id) {
console.log(e.target.id + ' and ' + filt.id)
return (this.setState({showButton: !this.state.showButton, hideButton: !this.state.hideButton}));
}
})
}
onShowButton = () => {
this.setState({showButton: !this.state.showButton, hideButton: !this.state.hideButton})
}
onValueIncrease = () => {
this.setState({aValue: this.state.aValue + 1});
}
onValueDecrease = () => {
this.setState({aValue: this.state.aValue - 1});
}
componentDidMount() {
fetch('http://localhost:3000/menu/food_category', {
method: 'get',
headers: {'content-type': 'application/json'}
})
.then(response => response.json())
.then(menudata => {
this.setState({category: menudata.menu_type})
console.log(this.state.category)
})
fetch('http://localhost:3000/menu', {
method: 'get',
headers: {'content-type': 'application/json'}
})
.then(response => response.json())
.then(menudata => {
this.setState({breads: menudata })
})
}
render() {
return (
<div>
{this.state.category.map(types => {
return (<div>
<div className="menu-head">{types}</div>
< div className="container-menu">
{this.state.breads.map((d, id)=> {
if (d.category === types) {
return (
<div>
<div className="content" key={id} id={d.id}>
<div className="items"> {d.item_name}</div>
<div className="prices"> {d.price} Rs.</div>
{this.state.showButton ?
<div>
<button
className="grp-btn-minus"
onClick={this.state.aValue <= 1 ?
() => this.onShowButton() :
() => this.onValueDecrease()}>-
</button>
<input className="grp-btn-text" type="text"
value={this.state.aValue} readOnly/>
<button id={d.id}
className="grp-btn-plus"
onClick={() => this.onValueIncrease()}>+
</button>
</div> :
<button id={d.id} key={id}
onClick={ this.onKeyCheck}
className="add-menu-btn">
add
</button>
}
</div>
</div>
)
}
})}
</div>
</div>)
})}
</div>
)
}
}
export default MenuCard;
This is the first image of multiple rendering of component Add buttons
Here is the problem that all buttons get updated on single click
You're using an array of items but refering to a single, shared value in handlers. De facto you're using a few shared values: showButton, hideButton, aValue), 2/3 unnecessary ;)
First - aValue for each item should be stored in a structure - array or object. It could be an order = {} - object with id-keyed properties with amounts as values like this:
order = {
'masala_id': 1,
'kebab_id' : 2
}
Event handler (for 'add') should check if id for choosen product already exist in order object (as property name) and update amount (+/-) or create new one with 1 value (and remove property when decreased amount = 0).
In practice order should also contain a price - it seams like duplicating data but it will be much easier to count total order value.
order = {
'masala_id': {
'amount': 1,
'price': 20,
},
'kebab_id' : {
'amount': 2,
'price': 180,
}
}
Item doesn't need to be a component but it's much easier to maintain it, keep it readable etc.
This way we can simply pass already ordered amount and conditionally render buttons:
<Product id={d.id}
name={d.item_name}
price={d.price}
amount={order[d.id] ? order[d.id].amount : 0 }
amountHandler={this.changeAmountHandler}
/>
Product should be slightly improved and simplified (f.e. key is needed on top div):
class Product extends React.Component {
render () {
const (id, name, price, amount, amountHandler} = this.props;
const showIncrease = !!amount; // boolean, it also means "don't show add button"
return (
<div key={id} >
<div className="content">
<div className="items">{name}</div>
<div className="prices">{price} Rs.</div>
{showIncrease ?
<div>
<button
className="grp-btn-minus"
onClick={(e) => { amountHandler(e, id, -1) }}
>-</button>
<input className="grp-btn-text" type="text"
value={amount}
readOnly/>
<button
className="grp-btn-plus"
onClick={(e) => { amountHandler(e, id, 1) }}
>+</button>
</div> :
<button
onClick={(e) => { amountHandler(e, id, 1) }}
className="add-menu-btn"
>add</button>
}
</div>
</div>
)}}
This way you can handle all events in one handler, keep entire order state in main component... in case of performance problems just use PureComponent.
It looks like all the buttons are sharing the same state. You could try breaking the button up into its own component, and then move the state that button needs into there. That way when you click a button the state of that one particular button is updated, and not the state of the parent component that contains all the buttons.

Reactjs Changing the state of another child component

My Parent class has two children
Counter component has state 'counter' which increments by the second;
class Counter extends Component {
constructor(props) {
super(props)
this.resetCount = this.resetCount.bind(this);
this.state = {
count : 0
}
}
resetCount() {
this.setState({
count : 0
});
}
componentDidMount() {
setInterval(() => {
this.setState({
count: this.state.count + 1
});
}, 1000);
}
render() {
const {count} = this.state;
const {color,size} = this.props;
return (
<Text style={{color, fontSize: size}}>{count}</Text>
);
}
}
In the Button Component, I have an onpress thing
<Button
onPress={resetCount}
title="Reset COunt"
color="#841584"
/>
In my main Parent Class I render
<Counter color={'green'} size={90} />
<Button/>
But I'm getting an error
'can't find variable resetCount' in App.js
You have to use 'this.resetCount' when using 'Button' inside Counter.render()
<Button
onPress={this.resetCount}
title="Reset COunt"
color="#841584"
/>
If Button is its own Component as mentioned you have to inherit the function onPress
Component Button
<Button onPress={this.props.onResetCount} ... />
Component Counter
render(){
return (
<Text style={{color, fontSize: size}}>{count}</Text>
<Button onResetCount={this.resetCount} title="Reset Count" color="... />
);
)
}
More detailed: https://reactjs.org/docs/faq-functions.html
This is due to Button not being able to access the class method inside its sibling Counter component. If your reorganise your code a little by moving the shared methods to the parent component you can a) achieve what you want, and b) make your code a little simpler. In other words make Counter the main component made up of two smaller dumb components / pure functions.
// No need for a component, just a function that returns
// a styled div
function Text({ count }) {
return <div>{count}</div>;
}
// Another function to return a button
function Button({ resetCount, text }) {
return <button onClick={resetCount}>{text}</button>;
}
// The main component that keeps the state which it passes
// to the dumb Text component
class Counter extends React.Component {
constructor() {
super()
this.state = { count: 0 };
this.resetCount = this.resetCount.bind(this);
}
componentDidMount() {
setInterval(() => {
this.setState({
count: this.state.count + 1
});
}, 1000);
}
resetCount() {
this.setState({ count: 0 });
}
render() {
return (
<div>
<Text count={this.state.count} />
<Button resetCount={this.resetCount} text="Reset count" />
</div>
)
}
}
ReactDOM.render(
<Counter />,
document.getElementById('container')
);
DEMO
You get the error because you can't do onPress={resetCount} this way. It is searching for the variable. But you don't have a variable, it's a function. So you should use this.resetCount if you want to access the function resetCount().
Here's an example how you can access the function of your parent component from the button in the child component:
// Parent component:
resetCount() {
// your code
}
render() {
return(
<Button resetCount={this.resetCount} /* your other stuff */ />
);
}
// Button component:
<button onPress={this.props.resetCount}>Click me</button>
Note: You can't update a sibling this way. You should move your functions from <Counter/> to your parent component.

Categories