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.
Related
i am tryign to write method for voting everytime user click button it should increment by 1 it is happening with below code
retro.js
export class RetroComponent extends Component {
constructor(props) {
super(props);
this.textareaRef = React.createRef();
this.state = {
value: 0
}
}
addCard(){
console.log("add card");
}
incrementWentWell() {
// eslint-disable-next-line react/no-direct-mutation-state
return ++this.state.value;
}
render() {
return (
<IconButton onClick={() => this.incrementWentWell()}>
<ThumbUpTwoToneIcon />
</IconButton>
<h5 style={{marginRight: 10}}><p>{this.state.value}</p></h5>
)}
}
That still counts as a state mutation (huge anti-pattern!). Ignoring the warning doesn't make the behavior change. Use a functional state update to take the existing state value, add 1 to it, and return a new state object so react can reconcile the change and update the UI/DOM.
incrementWentWell() {
this.setState(state => ({ value: state.value + 1 })
}
This should be pretty simple, but I can't figure out how to do it.
I have a component with multiple buttons, each with a "count" value, set with state. When a user clicks, the count goes up.
Right now, when I click one of the buttons, both counters change. How can I make it so only the div that was clicked updates, using the same state?
Edit: I don't want to have different counts, as I'd like for this component to render buttons dynamically. What if I don't know how many buttons I'll have at first?
class Block extends React.Component {
state = {
count: 0
};
handleClick = e => {
const count = this.state.count;
this.setState({ count: count + 1 });
};
render() {
return (
<div>
<button className="block" onClick={this.handleClick}>
<div className="counter">{this.state.count}</div>
</button>
<button className="block" onClick={this.handleClick}>
<div className="counter">{this.state.count}</div>
</button>
</div>
);
}
}
This is more of an issue of learning how to think in react.
If you need to be able to reuse a piece of functionality like a counter, you can make it its own component and have it manage its own state. Then you can reuse it wherever you need.
Here's an example:
class Counter extends React.Component {
state = {
count: 0
};
handleClick = () => {
// Use updater function when new state is derived from old
this.setState(prev => ({ count: prev.count + 1 }));
};
render() {
return (
<button className="block" onClick={this.handleClick}>
<div className="counter">{this.state.count}</div>
</button>
);
}
}
// Now you can use it dynamically like this:
class Block extends React.Component {
render() {
return (
<div>
<div>There are 4 counter component instances that each manage their own state.</div>
{[1,2,3,4].map(v => <Counter />)}
</div>
);
}
}
ReactDOM.render(<Block />, document.getElementById('root'));
<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>
<div id="root"></div>
you should define two state and when press each button update the current state and you can render the current state in the dome like this
state = {
firstCount: 0,
secondCount: 0
}
and write your action (function) to handle update state like this
handleUpdateCount = stateName => {
this.setState({
[stateName]= this.state[stateName] + 1
})
}
then you should called this function like this =>
this.handleUpdateCount('firstCount')
If your buttons are dynamic you can set your state to be an array and update the relevant index
class Block extends React.Component {
state = [];
handleClick = index => {
this.setState(state => {
const newState = [...state]; //keep state immutable
!newState[index] && (newState[index] = 0)
newState[index]++
return newState
});
};
render() {
return (
<div>
{[1,2,3].map((value, index) => <button className="block" onClick={() => this.handleClick(index)}>
<div className="counter">{this.state[index]}</div>
</button>)}
</div>
);
}
}
You have to use another value to update function when new state is derived from old state (like increment)
import React, { Component } from 'react'
export class Ref3 extends Component {
constructor(props) {
super(props)
this.state = {
count:0
}
}
//use prevState to help you update the old value to a new one
clickHandler=()=>{
this.setState((prevState=>({
count:prevState.count+1
})))
}
render() {
return (
<div>
<button onClick={this.clickHandler}>Click To Count</button>
{this.state.count}
</div>
)
}
}
export default Ref3
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.
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.
import React, { Component } from 'react';
import axios from 'axios';
import { Button, Card } from 'semantic-ui-react';
class Games extends Component {
state = { games:[], showGames: false }
componentDidMount() {
axios.get('/api/board_games')
.then(res => {
this.setState({games: res.data});
})
}
toggleGames = () => {
this.setState({ showGames: !this.state.showGames })
}
gamesList = () => {
const {games} = this.state
return games.map( game =>
<Card key={game.id}>
<Card.Content>
<Card.Header>{game.title}</Card.Header>
<Card.Description>Players: {game.min_players} - {game.max_players}</Card.Description>
<Card.Description>Company: {game.company}</Card.Description>
<Card.Description>Time Needed: {game.time_needed}</Card.Description>
</Card.Content>
<Card.Content extra>
<Button basic color='green'>
Add to Library
</Button>
</Card.Content>
</Card>
)
}
render() {
const showGames = this.state
return (
<div>
<h1>Games</h1>
<h3>Your Games</h3>
{ showGames ? (
<div>
<Card.Group itemsPerRow={4}>{this.gamesList()}</Card.Group>
</div>
)
: (
<button onClick={this.toggleGames()}>Add a Game</button>
)
}
</div>
)
}
}
export default Games;
In my mind, the render return should be checking if showGames is true or false. It's defaulted to false in the state at the beginning. For that reason, it should render the "add a game" button. But if you click that button, it should toggle the showGames to true and render the game cards. Instead, it automatically renders the cards when I arrive on the page. I would also like to add Done Adding to the first part of the if/else, but when I do that I get " Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops."
the way you're setting the onClick event is causing it to be constantly called. you should format it either like this:
onClick={this.toggleGames}
or like this:
onClick={() => this.toggleGames()}
Just edit the following line in your render() :
const showGames = this.state.showGames;
or
const { showGames } = this.state;
At the moment your showGames constant is an object instead of a boolean. Hence you are not able to use it conditionally.
Hope this helps!
you have set wrong onClick action from the button
// the onClick is executing in every render.
<button onClick={this.toggleGames()}>Add a Game</button>
// right way
button onClick={() => this.toggleGames()}>Add a Game</button>