I am facing a problem in resetting the count to 0 in my counter app. It's not resetting the count to 0 for any counter and I have tried console.log each counter.count but it's showing undefined.
App.js:
import React from "react";
import "./App.css";
import Counter from "./components/Counter";
export class App extends React.Component {
state = {
counters: []
};
addCounter = () => {
this.setState({
counters: [...this.state.counters, Counter]
});
};
reset = () => {
const counters = this.state.counters.map(counter => {
counter.count = 0;
return counter;
});
// console.log(counters.count);
this.setState({ counters });
};
render() {
return (
<div className="App">
<button onClick={this.reset}>Reset</button>
<button onClick={this.addCounter}>Add Counter</button>
{this.state.counters.map((Counter, index) => (
<Counter key={index} />
))}
</div>
);
}
}
export default App;
Counter.js:
import React, { Component } from 'react'
export class Counter extends Component {
state={
count:0,
}
increment=()=>{
this.setState({ count: this.state.count + 1 });
}
decrement=()=>{
this.setState({ count: this.state.count - 1 });
}
render() {
return (
<div>
<span><button onClick={this.increment}>+</button></span>
<span>{this.state.count}</span>
<span><button onClick={this.decrement}>-</button></span>
</div>
)
}
}
export default Counter
Reset counter should reset the count to 0 for all the counters.
When you use
this.setState({
counters: [...this.state.counters, Counter]
});
you saved a React Class into counters. So when you map to reset to 0, you need New Class to get state.count like this:
reset = () => {
const counters = this.state.counters.map(counter => {
(new counter()).state.count = 0;
return counter;
});
for (let counter of counters) {
console.log((new counter()).state);
}
this.setState({ counters });
};
It's time to lift the state up.
Move all your increment and decrement and reset functions to parent, App.
Also, add a separate array count state to monitor current count for each counter.
// App
export class App extends React.Component {
state = {
counters: [],
count: [] // additional state to store count for each counter
};
addCounter = () => {
this.setState({
counters: [...this.state.counters, Counter],
count: [...this.state.count, 0]
});
};
reset = () => {
this.setState({
count: this.state.count.map(c => 0)
});
};
// lifted up from Counter
increment = index => {
this.setState({
count: this.state.count.map((c, i) => (i === index ? c + 1 : c))
});
};
// lifted up from Counter
decrement = index => {
this.setState({
count: this.state.count.map((c, i) => (i === index ? c - 1 : c))
});
};
render() {
return (
<div className="App">
<button onClick={this.addCounter}>Add Counter</button>
<button onClick={this.reset}>Reset</button>
{this.state.counters.map((Counter, index) => (
<Counter
key={index}
increment={() => this.increment(index)}
decrement={() => this.decrement(index)}
count={this.state.count[index]}
/>
))}
</div>
);
}
}
// Counter
export class Counter extends React.Component {
render() {
return (
<div>
<span>
<button onClick={this.props.increment}>+</button>
</span>
<span>{this.props.count}</span>
<span>
<button onClick={this.props.decrement}>-</button>
</span>
</div>
);
}
}
Demo
Related
I have build a small application using class component and it's working fine also.
While I convert that application to function component to use react hooks, it's giving me error.
Please check and let me know where it went wrong.
function component
import React ,{useState} from 'react';
import './App.css';
import Counters from './component/counters';
import Navbar from './component/navbar';
function App(props) {
const initialState = [
{ id: 1, value: 0 },
{ id: 2, value: 10 },
{ id: 3, value: 20 },
{ id: 4, value: 30 },
];
const [counters, setCounters] = useState(initialState);
const handleIncrement = (counter) => {
// const counters = [...counters];
// const index = counters.indexOf(counter);
// counters[index] = { ...counter };
// counters[index].value++;
setCounters({ counter : counter.value +1 });
};
const handleDecrement = (counter) => {
// // const counters = [...counters];
// const index = counters.indexOf(counter);
// counters[index] = { ...counter };
// counters[index].value--;
// setCounters({ counters });
};
const handleDelete = (counterId) => {
// const counters = counters.filter((c) => c.id !== counterId);
// setCounters({ counters });
};
return (
<div className="container">
{/* <Navbar totalCounters={counters.reduce((a,c)=>a + c.value,0 )}/> */}
<Navbar totalCounters={counters.filter((c) => c.value > 0).count()}/>
<Counters
counters={counters}
onIncrement={handleIncrement}
onDecrement={handleDecrement}
onDelete={handleDelete}
/>
</div>
);
}
export default App;
class component
import './App.css';
import Counters from './component/counters';
import Navbar from './component/navbar';
import React from 'react';
class App extends React.Component {
state = {
counters: [
{ id: 1, value: 0 },
{ id: 2, value: 10 },
{ id: 3, value: 20 },
{ id: 4, value: 30 },
],
};
handleIncrement = (counter) => {
const counters = [...this.state.counters];
const index = counters.indexOf(counter);
counters[index] = { ...counter };
counters[index].value++;
this.setState({ counters });
};
handleDecrement = (counter) => {
const counters = [...this.state.counters];
const index = counters.indexOf(counter);
counters[index] = { ...counter };
counters[index].value--;
this.setState({ counters });
};
handleDelete = (counterId) => {
const counters = this.state.counters.filter((c) => c.id !== counterId);
this.setState({ counters });
};
render() {
return (
<div className="container">
<Navbar totalCounters={this.state.counters.reduce((a,c)=>a + c.value,0)}/>
<Counters
counters={this.state.counters}
onIncrement={this.handleIncrement}
onDecrement={this.handleDecrement}
onDelete={this.handleDelete} />
</div>
);
}
}
export default App;
In case, if you want to see the full code then just let me know.
Issue
The issue is your counters state is an array but you are mutating the state invariant to be an object in your handlers.
Solution
App - Use a functional state update to map the counters array from the previous state to the next state, using the id to match the specific counter you want to increment/decrement/delete. This shallow copies the previous array. Notice also that the counter being updated is also shallow copied into a new object reference. When deleting a counter you are correct to use .filter to remove the specific element and return a new array.
function App(props) {
const initialState = [
{ id: 1, value: 0 },
{ id: 2, value: 10 },
{ id: 3, value: 20 },
{ id: 4, value: 30 },
];
const [counters, setCounters] = useState(initialState);
const handleIncrement = (id) => {
setCounters(counters => counters.map(counter => counter.id === id
? {
...counter,
value: counter.value + 1,
}
: counter
));
};
const handleDecrement = (id) => {
setCounters(counters => counters.map(counter => counter.id === id
? {
...counter,
value: counter.value - 1,
}
: counter
));
};
const handleDelete = (id) => {
setCounters(counters => counters.filter((c) => c.id !== id));
};
return (
<div className="container">
<Navbar totalCounters={counters.filter((c) => c.value > 0).count()}/>
<Counters
counters={counters}
onIncrement={handleIncrement}
onDecrement={handleDecrement}
onDelete={handleDelete}
/>
</div>
);
}
Counters - pass the counter id to the handlers
class Counters extends Component {
render() {
const { onIncrement, onDecrement, onDelete, counters } = this.props;
return (
<div>
{counters.map((counter) => (
<Counter
key={counter.id}
counter={counter}
onIncrement={() => {
onIncrement(counter.id);
}}
onDecrement={() => {
onDecrement(counter.id);
}}
onDelete={() => {
onDelete(counter.id);
}}
/>
))}
</div>
);
}
}
Counter - since the id is closed in callback scope above just call the event handlers
class Counter extends Component {
...
render() {
return (
<div>
<span className={this.getBadge()}>{this.getCount()}</span>
<button
className="btn btn-secondary m-2"
onClick={this.props.onIncrement}
>
Increment
</button>
<button
className="btn btn-secondary m-2"
onClick={this.props.onDecrement}
>
Decrement
</button>
<button className="btn btn-danger m-2" onClick={this.props.onDelete}>
Delete
</button>
</div>
);
}
}
In your handleIncrement() function, you're setting an object, when the setCounters method is expecting an array of objects.
You can use ES6 destructuring. Also note the object key should be the id not the object.
I expect the below to fix the error.
setCounters([...counters, { counter.id : counter.value +1 }]);
And I believe below is the implementation you're looking for.
otherCounters = counters.filter(c => c.id != counter.id)
setCounters([...otherCounters, { counter.id : counter.value +1 }])
EDIT: One thing I noticed in your counters.jsx and counter.jsx is that you're calling a function with variables that do not expect one.
From counters.jsx
<Counter
...
// onIncrement of "Counter" (the 1st onIncrement on the left)
// is a function that do not expect a variable.
onIncrement={() => {onIncrement(counter);}}
/>
From counter.jsx
<button>
...
// "onIncrement" is called with "this.props.counter". However this "onIncrement" is not expecting a variable.
onClick={() => this.props.onIncrement(this.props.counter)}
</button>
For the correct syntax, you can change either, but as example you can change counter.jsx to
<button>
...
onClick={() => this.props.onIncrement()}
</button>
In functional component on react when you wanna make state you must be use useState, you can follow my code here:
import './App.css';
import Counters from './component/counters';
import Navbar from './component/navbar';
import { useState } from 'react';
const App = () => {
const [countersState, setCountersState] = useState([
{ id: 1, value: 0 },
{ id: 2, value: 10 },
{ id: 3, value: 20 },
{ id: 4, value: 30 },
]);
const handleIncrement = (counter) => {
const counters = countersState
const index = counters.indexOf(counter);
counters[index] = { ...counter };
setCountersState(counters);
};
const handleDecrement = (counter) => {
const counters = countersState
const index = counters.indexOf(counter);
console.log(index)
counters[index] = { ...counter };
counters[index].value--;
setCountersState(counters);
};
const handleDelete = (counterId) => {
const counters = countersState.filter((c) => c.id !== counterId);
setCountersState(counters)
}
return (
<div className="container">
<Navbar totalCounters={this.state.counters.reduce((a,c)=>a + c.value,0)}/>
<Counters
counters={countersState}
onIncrement={handleIncrement}
onDecrement={handleDecrement}
onDelete={handleDelete} />
</div>
);
};
export default App;
I created a increment/decrement function, but I have a doubt.
I can decrement count clicking in same button that include a increment count?
How to create that function?
Code:
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
clicks: 0,
show: true
};
}
IncrementItem = () => {
this.setState({ clicks: this.state.clicks + 1 });
}
DecreaseItem = () => {
this.setState({ clicks: this.state.clicks - 1 });
}
ToggleClick = () => {
this.setState({ show: !this.state.show });
}
render() {
return (
<div>
<button onClick={this.IncrementItem}>Click to increment by 1</button>
<button onClick={this.DecreaseItem}>Click to decrease by 1</button>
<button onClick={this.ToggleClick}>
{ this.state.show ? 'Hide number' : 'Show number' }
</button>
{ this.state.show ? <h2>{ this.state.clicks }</h2> : '' }
</div>
);
}
}
export default App;
You could set a conditional in the function you trigger when you click on the button. Maybe something like this:
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
counter: 0,
clicked: false,
};
}
toggleClicked = () => {
const counter = this.state.clicked ? counter +1 : counter - 1;
const clicked = !this.state.clicked;
this.setState({ counter, clicked })
}
render() {
return (
<div>
<button onClick={this.toggleClicked}>Click</button>
{ this.state.counter ? <h2>{ this.state.counter }</h2> : '' }
</div>
);
}
}
export default App;
This way if you have already clicked the counter will decrease by 1 and viceversa.
I am working with an API such that on clicking button "show more" there's some counter which increases itself by 25 and display next content:
constructor(props){
this.state = { counter: 0}}
showMore = () => {
axios.get(some_random_link/append/${this.state.counter + 25}/0
}.then(res => {
this.setState({ counter: this.state.counter + 25 });
});
render(){
return(
<div>
<button onClick={this.showMore}>show more</button>
</div>
Expected:
some_random_link/append/25/0
some_random_link/append/50/0
some_random_link/append/75/0
Actual:
some_random_link/append/25/0
some_random_link/append/25/0
some_random_link/append/25/0
setState is an async process, hence when you want to update a state by using the previous one then do it this way
class Counter {
constructor(props) {
this.state = { counter: 0 };
}
_showMore = () => {
const { counter } = this.state;
axios.get(`some_random_link/append/${counter + 25}/0`).then(res => {
this.setState(prevState => ({ counter: prevState.counter + 25 }));
});
};
render() {
return (
<div>
<button onClick={this._showMore}>show more</button>
</div>
);
}
}
Call axios call after setState()
class Counter {
constructor(props) {
this.state = { counter: 0 };
}
showMore = () => {
this.setState(
({ counter }) => ({ counter: counter + 25 }),
() => {
axios.get(`some_random_link/${this.state.counter}/0`); // this.state.counter already updated
}
);
};
render() {
return (
<div>
<button onClick={this.showMore}>show more</button>
</div>
);
}
}
this.setState() is asynchronous! so instead of this.setState({})
change it to
this.setState(prev => counter : prev.counter + 25)
For more details check this link:
Beware: React setState is asynchronous!
class App extends React.Component {
constructor(props){
super(props)
this.state={counter:0};
}
showMore = () => {
axios
.get(`/fake/fake/fake/${this.state.counter}`)
.then(() => {
console.log("fake data");
})
.catch(() => {
console.log(this.state.counter)
this.setState(prevState => ({ counter: prevState.counter + 25 }));
});
};
render(){
return (
<span style={{background:"green",color:"#fff",padding:"15px",cursor:"pointer"}}onClick={this.showMore}>PLZ CLICK ME</span>
)
}
}
ReactDOM.render(<App />, document.querySelector('#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>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<div id="root"></div>
I tried to bulid shopping cart in React I don't use redux so I think it can be a problem too. I have now alomost done application so I want to finish it in this way without using redux. Ok whats the problem. I made function to add items into the shopping cart in component counter but I don't want to display this products in this component but in main component App in header. In component counter I creat component ShoppingCart to display the products - but I want to only push products into the ShoppingCart but display them in component App.
I tried a lot of diffrent methods but it's not working. I can display products but really not in the place I want. I think the problem is how I can comunicate with my items between components.
This is my Counter
import React, { Component } from "react";
import "./Counter";
import "./Counter.css";
import ShoppingCart from "./ShoppingCart";
class Counter extends Component {
state = {
availableProducts: 20,
shoppingCart: 0,
cart: []
};
handleRemoveFromCart = () => {
this.setState({
shoppingCart: this.state.shoppingCart - 1
});
};
handleAddToCart = () => {
this.setState({
shoppingCart: this.state.shoppingCart + 1
});
};
handleAddProductsToCart = props => {
// console.log("clicked", this.props.name, this.state.shoppingCart)
let found = false;
const updateCart = this.state.cart.map(cartItem => {
if (cartItem.name === this.props.name) {
found = true;
cartItem.productsNumber = this.state.shoppingCart;
return cartItem;
} else {
return cartItem;
}
});
if (!found) {
updateCart.push({
name: this.props.name,
productsNumber: this.state.shoppingCart,
key: this.props.name
});
}
this.setState({
cart: updateCart
});
// return <ShoppingCart cart={updateCart} />;
// console.log(updateCart);
};
render() {
const cart = this.state.cart.map(cartItem => (
<ShoppingCart
name={cartItem.name}
productsNumber={cartItem.productsNumber}
key={cartItem.key}
/>
));
return (
<>
<div className="counter">
<button
className="buttonCount"
disabled={this.state.shoppingCart === 0 ? true : false}
onClick={this.handleRemoveFromCart}
>-</button>
<span> {this.state.shoppingCart} </span>
<button
className="buttonCount"
disabled={
this.state.shoppingCart === this.state.availableProducts
? true
: false
}
onClick={this.handleAddToCart}
>
+
</button>
<button
className="buy"
disabled={this.state.shoppingCart <= 0 ? true : false}
onClick={this.handleAddProductsToCart}
>Add to cart</button>
</div>
<div>{cart}</div>
</>
);
}
}
export default Counter;
and this is Shopping
import React, {Component} from "react"
import "./ShoppingCart";
import "./ShoppingCart.css";
class ShoppingCart extends Component {
render() {
return (
<>
<div>{this.props.name}</div>
<div>{this.props.productsNumber}</div>
</>
);
}
}
export default ShoppingCart;
If you have any suggestions it will be helpful. Thank you.
I think the following code can help you
import React, { Component } from "react";
import "./Counter";
import "./Counter.css";
import ShoppingCart from "./ShoppingCart";
class Counter extends Component {
state = {
availableProducts: 20,
shoppingCart: 0,
cart: []
};
handleRemoveFromCart = () => {
this.setState({
shoppingCart: this.state.shoppingCart - 1
});
};
handleAddToCart = () => {
this.setState({
shoppingCart: this.state.shoppingCart + 1
});
};
handleAddProductsToCart = props => {
// console.log("clicked", this.props.name, this.state.shoppingCart)
let found = false;
const updateCart = this.state.cart.map(cartItem => {
if (cartItem.name === this.props.name) {
found = true;
cartItem.productsNumber = this.state.shoppingCart;
return cartItem;
} else {
return cartItem;
}
});
if (!found) {
updateCart.push({
name: this.props.name,
productsNumber: this.state.shoppingCart,
key: this.props.name
});
}
this.setState({
cart: updateCart
});
// return <ShoppingCart cart={updateCart} />;
// console.log(updateCart);
};
CreateCard=(cartItem)=>{
console.log(cartItem)
return(
<ShoppingCart
name={cartItem.name}
productsNumber={cartItem.productsNumber}
key={cartItem.key}
/>
);
}
render() {
return (
<>
<div className="counter">
<button
className="buttonCount"
disabled={this.state.shoppingCart === 0 ? true : false}
onClick={this.handleRemoveFromCart}
>-</button>
<span> {this.state.shoppingCart} </span>
<button
className="buttonCount"
disabled={
this.state.shoppingCart === this.state.availableProducts
? true
: false
}
onClick={this.handleAddToCart}
>
+
</button>
<button
className="buy"
disabled={this.state.shoppingCart <= 0 ? true : false}
onClick={this.handleAddProductsToCart}
>Add to cart</button>
</div>
<div>{this.state.cart!=null?this.state.cart.map(cartItem => (this.CreateCard(cartItem))):""}</div>
</>
);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.3.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.3.0/umd/react-dom.production.min.js"></script>
I hope help you
I'm planning to make "online shopping cart" template in react. However, I can't figure out how to hide "counters" element when "count" reaches zero
I managed to make separated button which hides element on click, but that's not suitable for this project.
counter.jsx
import React, { Component } from 'react';
import Counters from './counters';
class Counter extends Component {
state = {
count: 1
};
handleIncrement = () => {
this.setState({count: this.state.count + 1});
}
handleDelete = () => {
this.setState({count: this.state.count - 1});
if (this.state.count == 0) {
this.state.counters = {
isHidden: true
}
}
}
render() {
let classes = 'badge m-2 badge-';
classes += this.state.count === 1 ? 'primary' : 'primary';
return (
<div>
<span className={classes}>{this.formatCount()}</span>
<button onClick={() => this.handleIncrement()} className='btn btn-primary m-2'>+</button>
<button onClick={() => this.handleDelete()} className="btn btn-danger m-2">-</button>
</div>
);
}
formatCount() {
const {count} = this.state;
return count === 0 ? 'Reached' : count;
}
}
export default Counter;
counters.jsx
import React, { Component } from 'react';
import Counter from './counter';
class Counters extends Component {
state = {
counters: [
{ id: 1, value: 1 },
{ id: 2, value: 1 },
{ id: 3, value: 1 },
{ id: 4, value: 1 }
]
}
render() {
return (
<div>
{this.state.counters.map(
counter => (
<Counter key={counter.id} value={counter.value} id={counter.id} />
))}
</div>
);
}
}
export default Counters;
So, when delete button is pressed it should go from 1 to 0, and when it reaches zero, counters element should disappear(based on Id).
Generally https://stackoverflow.com/a/54649508/10868273 answer should be the way forward but you in order not to change too much you could try this (i've included only the changes you want to make)
counter.jsx
handleIncrement = () => {
this.setState((state) => ({
count: state.count + 1
}));
}
handleDelete = () => {
this.setState((state) => ({
count: state.count - 1
}),() => {
if (this.state.count <= 0){
this.props.handleCounterRemove(this.props.id);
}
});
}
Counters.jsx
import React, { Component } from 'react';
import Counter from './counter';
class Counters extends Component {
state = {
counters: [
{ id: 1, value: 1 },
{ id: 2, value: 1 },
{ id: 3, value: 1 },
{ id: 4, value: 1 }
]
}
handleCounterRemove = (counterId) =>{
const { counters } = this.state;
this.setState((state)=> ({
counters: state.counters.filter(counter => counter.id !== counterId)
}));
}
render() {
return (
<div>
{this.state.counters.map(
counter => (
<Counter key={counter.id} value={counter.value} id={counter.id} handleCounterRemove={this.handleCounterRemove} />
))}
</div>
);
}
}
export default Counters;
As has been mentioned in the other answers, you do not want to just call setState if the next value depends on the pervious one: you can read more on this here React SetState documentation
Also, notice the introduction of handleCounterRemove in the Counters component it's like the only way a child component can affect it's parent component
As Joe mentioned in the comments, you are mutating the state directly, which is very anti-pattern. Always use setState().
Also, whenever you are setting a new state that depends on a previous state variable, pass in a callback to setState() rather than an object. This can save you a few headaches in certain cases.
handleDelete = () => {
this.setState(prevState => ({
count: prevState.count - 1,
isHidden: prevState.count === 1 // Hide if we are decreasing count from 1 to 0
}));
}
Keep Counter as a stateless component. Store the counter state in the Counters component itself. Move the increment, decrement functions to Counters component. In decrement function, write logic such that when it hits 0, remove the corresponding counter object from this.state.counters.
You can also return null from your render() method in counter.jsx, when this.state.coun is 0
You should redevelop the method handleDelete. Here is the possible version
handleDelete = () => {
let currentCount = this.state.count - 1;
this.setState({
count: currentCount
counters: {
isHidden: currentCount === 0
}
});
}