I am new to ReactJS. I am trying to build an online shopping type of feature wherein we can increment the quantity and decrement it. Or directly remove an item using 'delete' button. I am trying to print the id of the button which is clicked, using console.log("delete clicked",counterId); in "counters.jsx". But some how it gives me undefined each time. Here is my code.
counters.jsx
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:3},
{id:4,value:0}
]
};
handleDelete=(counterId)=>{
console.log("delete clicked",counterId);
}
render(){
return (
<div>
{this.state.counters.map(counter=>
<Counter key={counter.id} onDelete={this.handleDelete} value={counter.value}/>)}
</div>
);
}
}
export default Counters;
counter.jsx
import React, { Component} from 'react';
class Counter extends Component{
state={
value: this.props.value
};
handleIncrement=()=>{
this.setState({value:this.state.value+1});
}
handleDecrement=()=>{
this.setState({value:this.state.value-1});
}
render(){
return (
<React.Fragment>
<p>
<span className={this.getBadgeClasses()}>{this.formatCount()}</span>
<button onClick={this.handleIncrement} className="btn btn-secondary btn-sm m-1">Increment</button>
<button onClick={this.handleDecrement} className="btn btn-warning btn-sm m-1">Decrement</button>
<button onClick={()=>this.props.onDelete(this.props.id)} className="btn btn-danger btn-sm m-1">Delete</button>
</p>
</React.Fragment>
);
}
getBadgeClasses(){
let classes="badge m-2 badge-";
classes+=this.state.value===0?"warning":"primary";
return classes;
}
formatCount(){
const {value} = this.state;
return value===0 ? 'Zero': value;
}
}
export default Counter;
Any help will be much appreciated!
Read this once.
https://reactjs.org/docs/lists-and-keys.html#keys-must-only-be-unique-among-siblings
With the example above, the Post component can read props.id, but not props.key.
Therefore, you have to add props.id={counter.id}!
counters.jsx
before
render(){
return (
<div>
{this.state.counters.map(counter=>
<Counter key={counter.id} onDelete={this.handleDelete} value={counter.value}/>)}
</div>
);
}
after
render(){
return (
<div>
{this.state.counters.map((counter, index)=>
<Counter key={index} id={counter.id} onDelete={this.handleDelete} value={counter.value}/>)}
</div>
);
}
You are not passing counter as a prop to your Counter component, you are just passing counter.value. Instead of doing this pass the counter itself:
<Counter key={counter.id} onDelete={this.handleDelete} counter={counter} /> )}
Then in Counter component:
state={
value: this.props.counter.value,
};
Also, if you separate your handleDelete function here and use its reference, it is not recreated in every render. Like:
handleDelete = () => this.props.onDelete( this.props.counter.id );
and
<button onClick={this.handleDelete} className="btn btn-danger btn-sm m-1">Delete</button>
But, your logic is somehow weird. You have a state in parent component, then you also keep another state in the child and do the increments, decrements there. Your parent's state does not change. Is that what you really want? So, you want multiple counters and keep their state separately like that?
Here is the full code:
class Counters extends React.Component {
state = {
counters: [
{ id: 1, value: 4 },
{ id: 2, value: 0 },
{ id: 3, value: 3 },
{ id: 4, value: 0 },
],
};
handleDelete=( counterId ) => {
console.log( "delete clicked", counterId );
}
render() {
return (
<div>
{this.state.counters.map( counter =>
<Counter key={counter.id} onDelete={this.handleDelete} counter={counter} /> )}
</div>
);
}
}
class Counter extends React.Component {
state={
value: this.props.counter.value,
};
handleIncrement=() => {
this.setState( { value: this.state.value + 1 } );
}
handleDecrement=() => {
this.setState( { value: this.state.value - 1 } );
}
handleDelete = () => this.props.onDelete( this.props.counter.id );
render() {
return (
<div>
<p>
<span className={this.getBadgeClasses()}>{this.formatCount()}</span>
<button onClick={this.handleIncrement} className="btn btn-secondary btn-sm m-1">Increment</button>
<button onClick={this.handleDecrement} className="btn btn-warning btn-sm m-1">Decrement</button>
<button onClick={this.handleDelete} className="btn btn-danger btn-sm m-1">Delete</button>
</p>
</div>
);
}
getBadgeClasses() {
let classes = "badge m-2 badge-";
classes += this.state.value === 0 ? "warning" : "primary";
return classes;
}
formatCount() {
const { value } = this.state;
return value === 0 ? "Zero" : value;
}
}
ReactDOM.render(
<Counters />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
In case of you want to see here is the alternative approach:
class Counters extends React.Component {
state = {
counters: [
{ id: 1, value: 4 },
{ id: 2, value: 0 },
{ id: 3, value: 3 },
{ id: 4, value: 0 },
],
};
handleDelete = ( counter ) => {
const newCounters = this.state.counters.filter( el => el.id !== counter.id );
this.setState( { counters: newCounters } );
}
handleCounter = ( counter, direction ) => {
const newCounters = this.state.counters.map( ( el ) => {
if ( el.id !== counter.id ) { return el; }
return direction === "up" ? { ...counter, value: counter.value + 1 }
: { ...counter, value: counter.value - 1 };
} );
this.setState( { counters: newCounters } );
}
render() {
return (
<div>
{this.state.counters.map( counter =>
( <Counter
key={counter.id}
onDelete={this.handleDelete}
counter={counter}
handleCounter={this.handleCounter}
/> ) )}
</div>
);
}
}
const Counter = ( props ) => {
const { counter, handleCounter, onDelete } = props;
function handleIncrement() {
handleCounter( counter, "up" );
}
function handleDecrement() {
handleCounter( counter );
}
function handleDelete() { onDelete( counter ); }
function getBadgeClasses() {
let classes = "badge m-2 badge-";
classes += counter.value === 0 ? "warning" : "primary";
return classes;
}
function formatCount() {
const { value } = counter;
return value === 0 ? "Zero" : value;
}
return (
<div>
<p>
<span className={getBadgeClasses()}>{formatCount()}</span>
<button onClick={handleIncrement} className="btn btn-secondary btn-sm m-1">Increment</button>
<button onClick={handleDecrement} className="btn btn-warning btn-sm m-1">Decrement</button>
<button onClick={handleDelete} className="btn btn-danger btn-sm m-1">Delete</button>
</p>
</div>
);
};
ReactDOM.render(
<Counters />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Related
It's my first day learning react and I'm stuck with an issue (I'm following Mosh's tutorial):
import React, { Component } from "react";
class Counter extends Component {
state = {
value: this.props.value,
};
handleIncrement = () => {
console.log("Click!");
this.setState({ value: this.state.value + 1 });
};
handleDecrement = () => {
console.log("Click!");
if (this.state.value !== 0) {
this.setState({ value: this.state.value - 1 });
}
};
render() {
return (
<div className="row align-items-center">
<div className="col">
<span className={this.getBadgeClasses()}>{this.formatCount()}</span>
</div>
<div className="col">
<button
onClick={() => this.handleIncrement({ id: 1 })}
className="btn btn-dark"
>
+
</button>
<button
onClick={() => this.handleDecrement({ id: 1 })}
className={this.isLessThanZero()}
>
-
</button>
</div>
<div className="col">
<button
onClick={() => this.props.onDelete(this.props.id)}
className="btn btn-danger m-2"
>
Delete
</button>
</div>
</div>
);
}
isLessThanZero() {
let classes = "btn btn-dark ";
classes += this.state.value === 0 ? "disabled" : "";
return classes;
}
getBadgeClasses() {
let classes = "badge m-2 badge-";
classes += this.state.value === 0 ? "warning" : "primary";
return classes;
}
formatCount() {
let { value } = this.state;
return value === 0 ? <h1>Zero</h1> : value;
}
}
export default Counter;
This is a counter component that just responds to the buttons. I'm including those in another component:
import React, { Component } from "react";
import Counter from "./counter";
class Counters extends Component {
state = {
counters: [
{ id: 1, value: 0 },
{ id: 2, value: 0 },
{ id: 3, value: 0 },
{ id: 4, value: 0 },
],
};
handleDelete = (counterId) => {
console.log("Event detected! Delete", counterId);
};
render() {
return (
<div className="cont">
{this.state.counters.map((counter) => (
<Counter
value={counter.value}
key={counter.id}
onDelete={this.handleDelete}
></Counter>
))}
</div>
);
}
}
export default Counters;
In the handleDelete function, when called, I'm getting undefined for the counterId. When I check in the ReactComponents Chrome extention, I see that there isn't any ID:
Why is this happening?
The problem is you are not passing the counter for this.handleDelete. You need to explicitly pass it.
<Counter
value={counter.value}
key={counter.id}
onDelete={() => this.handleDelete(counter.id)}
/>
In the above snippet, I am passing a new function to the Counter component, the function just calls this.handleDelete with the counter.id of the corresponding component.
I have a dynamic navigation removable tabs using Fluent for react
I would like that when I close a tab ( for example test3) , the focus gets on the last tab in the nav bar like bellow
my actual problem is that when I close a tab , I loose the focus.
Here's my code
import React from "react";
import { Button, Menu, tabListBehavior } from "#fluentui/react-northstar";
import { CloseIcon } from "#fluentui/react-icons-northstar";
class MenuExampleTabShorthand extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedIndex: 0
};
}
items = [
{
key: "editorials",
content: (
<div>
"test"
<Button
icon={<CloseIcon />}
text
iconOnly
title="Close"
onClick={() => this.closeClick("editorials")}
/>
</div>
)
},
{
key: "review",
content: (
<div>
"test2"
<Button
icon={<CloseIcon />}
text
iconOnly
title="Close"
onClick={() => this.closeClick("review")}
/>
</div>
)
},
{
key: "events",
content: (
<div>
"test3"
<Button
icon={<CloseIcon />}
text
iconOnly
title="Close"
onClick={() => this.closeClick("events")}
/>
</div>
)
}
];
closeClick = task => {
this.setState(function(prev, props) { // Im setting the selectedIndex to 0
return { ...prev, selectedIndex:0 };
});
this.items = this.items.filter(elm => elm.key !== task);
};
render() {
return (
<Menu
activeIndex={this.state.selectedIndex}
onActiveIndexChange={(i, j) => {
this.setState(function(prev, props) {
return { ...prev, selectedIndex: j.activeIndex };
});
}}
items={this.items}
underlined
primary
accessibility={tabListBehavior}
aria-label="Today's events"
/>
);
}
}
export default MenuExampleTabShorthand;
Here's a reproduction of error demo
The issue you are facing is caused by event propagation, you can fix it by adding e.stopPropagation(); in close click event handler, and not having it will cause the active item click handler to fire and then set the current active item to the one removed (codesandbox), note that I'm passing the event object to closeClick:
import React from "react";
import { Button, Menu, tabListBehavior } from "#fluentui/react-northstar";
import { CloseIcon } from "#fluentui/react-icons-northstar";
class MenuExampleTabShorthand extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedIndex: 0
};
}
items = [
{
key: "editorials",
content: (
<div>
"test"
<Button
icon={<CloseIcon />}
text
iconOnly
title="Close"
onClick={e => this.closeClick("editorials", e)}
/>
</div>
)
},
{
key: "review",
content: (
<div>
"test2"
<Button
icon={<CloseIcon />}
text
iconOnly
title="Close"
onClick={e => this.closeClick("review", e)}
/>
</div>
)
},
{
key: "events",
content: (
<div>
"test3"
<Button
icon={<CloseIcon />}
text
iconOnly
title="Close"
onClick={e => this.closeClick("events", e)}
/>
</div>
)
}
];
closeClick = (task, e) => {
e.stopPropagation();
this.setState(function(prev, props) {
return { ...prev, selectedIndex: 0 };
});
console.log(this.items);
this.items = this.items.filter(elm => elm.key !== task);
console.log(this.items);
};
render() {
return (
<Menu
activeIndex={this.state.selectedIndex}
onActiveIndexChange={(i, j) => {
this.setState(function(prev, props) {
return { ...prev, selectedIndex: j.activeIndex };
});
}}
items={this.items}
underlined
primary
accessibility={tabListBehavior}
aria-label="Today's events"
/>
);
}
}
export default MenuExampleTabShorthand;
I want to make a counter app with increment, decrement and add counter button. But add counter function is not working. It's showing this error:
Parsing error: Adjacent JSX elements must be wrapped in an enclosing tag. Did you want a JSX fragment <>...?
i have already tried enclosing it in tags,still its not working.
import React,{Component} from 'react';
import './App.css';
export class App extends Component {
state={
count:0,
}
increment=()=>{
this.setState({ count: this.state.count + 1 });
}
decrement=()=>{
this.setState({ count: this.state.count - 1 });
}
addCounter=()=>{
<span><button onClick={this.increment}>+</button></span>
<span>{this.state.count}</span>
<span><button onClick={this.decrement}>-</button></span>
}
render() {
return (
<div className="App">
<button onClick={this.addCounter}>Add Counter</button>
</div>
)
}
}
export default App;
add counter function should add another counter just below the previous counter.
Basically extract a Counter component.
Then have your App maintain a list of Counter components.
// Counter Component
export class Counter extends React.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>
);
}
}
// App Component
export class App extends React.Component {
state = {
counters: [], // additional state for Counter components
}
addCounter = () => {
this.setState({
counters: [
...this.state.counters,
Counter
]
})
}
render() {
return (
<div className="App">
<button onClick={this.addCounter}>Add Counter</button>
{ this.state.counters.map((Counter, index) => (
<Counter key={index} />)
)}
</div>
)
}
}
Demo
You are just adding more spans and button but refering to the same counter.
state={
i : 0,
count:0,
}
var newCount="counter"+this.state.i;
this.setState({
i : this.state.i+1,
})
this.setState({
count: {
...this.state.count,
newCount: 0
}
});
So with this you add a new counter with a progresive autoincrement number.
I think it is what you want to do.
const { useState } = React;
function App(){
const [counter, setCounter] = useState([]);
function addCounter(){
setCounter(counter.concat({id: counter.length, count: 0}));
}
function increase(id){
setCounter(counter.map(el=>{
if(el.id === id){
el.count++;
}
return el;
}));
}
function decrease(id){
setCounter(counter.map(el=>{
if(el.id === id){
el.count--;
}
return el;
}));
}
return (
<div className="App">
<button onClick={addCounter}>Add Counter</button>
{
counter.map(el=>{
return(
<div key={el.id}>
<span>Counter #{el.id}</span>
<div>
<button onClick={()=>{increase(el.id)}}>+</button>
<span>{el.count}</span>
<button onClick={()=>{decrease(el.id)}}>-</button>
</div>
</div>
)
})
}
</div>
)
}
ReactDOM.render(
<App />, document.getElementById('root')
)
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
Your "counter" needs to be a react component with its own state, what you have there will have each "counter" you add use the same component state from App. You also do not save the returned JSX to then render anywhere.
const Counter = () => {
const [count, setCount] = useState(0);
return (
<div> // <-- React components can return only a single node*
<span>
<button onClick={() => setCount(count + 1)}>+</button>
</span>
<span>{count}</span>
<span>
<button onClick={() => setCount(count - 1)}>-</button>
</span>
</div>
);
};
class App extends Component {
state = {
counters: []
};
addCounter = () => {
this.setState(prevState => ({
counters: [...prevState.counters, <Counter />]
}));
};
render() {
return (
<div>
<button onClick={this.addCounter}>Add Counter</button>
{this.state.counters}
</div>
);
}
}
* React render lifecycle function can render arrays.
You have to add another functional Component instead of adding JSX. DEMO
import React from 'react';
const counter = (props) => {
return (
<div>
<span><button onClick={() => props.increment(props.index)}>+</button></span>
<span>{props.count}</span>
<span><button onClick={() => props.decrement(props.index)}>-</button></span>
</div>
)
}
export default counter;
And your main App component
import React, {Component} from 'react';
import Counter from './Counter';
import './App.css';
export class App extends Component {
state={
counters: []
}
valueChanger = (index, inc) => {
this.setState((prevState) => {
const counters = prevState.counters.slice();
counters[index] += inc;
return {
counters: counters
}
});
}
increment=(index)=>{
this.valueChanger(index, 1);
}
decrement=(index)=>{
this.valueChanger(index, -1);
}
addCounter=()=>{
this.setState((prevState) => {
return { counters: [...prevState.counters, 0] }
});
}
render() {
let counterElems = this.state.counters.map((c, index) => {
return <Counter key={index} index={index} increment={this.increment} decrement={this.decrement} count={c} />
});
return (
<div className="App">
{counterElems}
<button onClick={this.addCounter}>Add Counter</button>
</div>
)
}
}
export default App;
I am building a Treeview application in react and I have some problem that I am not solving.
When I click Apply button I want to push in a array all selected values, but when I click Cancel button I want to remove all the last selected values.
My code is as below, please can anyone modify the code and help the for the solution?
export default class TreeView extends Component {
constructor(props) {
super(props);
this.state = {
checked: [],
expanded: [],
keyword:"",
pushOn:[],
};
}
onSearchInputChange = (event, data, searchedNodes) => {
this.setState(prevState => {
if (prevState.keyword.trim() && !data.value.trim()) {
return {
expanded: [],
keyword: data.value
};
}
return {
expanded: this.getAllValuesFromNodes(searchedNodes, true),
keyword: data.value
};
});
};
getHighlightText = (text, keyword) => {
const startIndex = text.indexOf(keyword);
return startIndex !== -1 ? (
<span>
{text.substring(0, startIndex)}
<span style={{ color: "#2cb664" }}>
{text.substring(startIndex, startIndex + keyword.length)}
</span>
{text.substring(startIndex + keyword.length)}
</span>
) : (
<span>{text}</span>
);
};
keywordFilter = (nodes, keyword) => {
let newNodes = [];
for (let n of nodes) {
if(n.children) {
const nextNodes = this.keywordFilter(n.children, keyword);
if (nextNodes.length > 0) {
n.children = nextNodes;
} else if (n.label.toLowerCase().includes(keyword.toLowerCase())) {
n.children = nextNodes.length > 0 ? nextNodes : [];
}
if (
nextNodes.length > 0 ||
n.label.toLowerCase().includes(keyword.toLowerCase())
) {
n.label = this.getHighlightText(n.label, keyword);
newNodes.push(n);
}
}else {
if(n.label.toLowerCase().includes(keyword.toLowerCase())) {
n.label = this.getHighlightText(n.label, keyword);
newNodes.push(n);
}
}
}
return newNodes;
};
getAllValuesFromNodes = (nodes, firstLevel) => {
if (firstLevel) {
const values = [];
for (let n of nodes) {
values.push(n.value);
if(n.children) {
values.push(...this.getAllValuesFromNodes(n.children, false));
}
}
return values;
} else {
const values = [];
for (let n of nodes) {
values.push(n.value);
if(n.children) {
values.push(...this.getAllValuesFromNodes(n.children, false));
}
}
return values;
}
};
shouldComponentUpdate(nextProps, nextState) {
if(this.state.keyword !== nextState.keyword) {
return true;
}
if ( !lodash.isEqual(this.state.checked, nextState.checked)) {
return true;
}
if ( !lodash.isEqual(this.state.expanded, nextState.expanded)) {
return true;
}
return true;
}
render () {
let searchedNodes = this.state.keyword.trim()
? this.keywordFilter(lodash.cloneDeep(nodesData), this.state.keyword)
: nodesData;
return (
<div style={{marginLeft:"30px", marginTop:"30px", width:"30%"}}>
<div className="search">
<Input style={{marginBottom:"10px", width:"100%"}}
icon="fas fa-search"
placeholder="Search Categories"
iconPosition="left"
onChange={(event, data) => {
this.onSearchInputChange(event, data, searchedNodes);
}}
className="Change"
/>
</div>
<hr></hr>
<div className="checkbox-tree">
<CheckboxTree
nodes={searchedNodes}
checked={this.state.checked}
expanded={this.state.expanded}
onCheck={checked => this.setState({ checked })}
onExpand={expanded => this.setState({ expanded })}
expandOnClick
onClick = { () => { console.log("clicked"); }}
showNodeIcon={false}
icons={{
expandClose: <i class="fas fa-chevron-right fa-xs"></i>,
expandOpen: <i class="fas fa-chevron-down fa-xs"></i>,
}}
nameAsArray={true}
/>
</div>
<div>
<form class="butt">
<button type="button" onClick={this.cancel}>Cancel</button>
<button onClick={this.pushOnArray}>Apply</button>
</form>
</div>
</div>
)
}
}
Thanks in advance!
So I don't know what your project setup is but the easiest way to go with what I know is:
export default class TreeViewParent extends Component {
constructor(props) {
super(props);
this.state = {
checked: [],
};
onSave = (newCheckedValues) => {
this.setState({checked: newCheckedValues})
// perform action based on selected values here
}
render() {
return <TreeView checked={this.state.checked} save={this.onSave} />
}
}
// I am omitting parts of this component that are not related to the problem
export default class TreeView extends Component {
constructor(props) {
super(props);
this.state = {
checked: this.props.checked,
expanded: [],
keyword: "",
pushOn:[],
};
}
cancel = () => {
this.props.save(this.props.checked)
this.setState({checked: this.props.checked})
}
save = () => {this.props.save(this.state.checked)}
render () {
let searchedNodes = this.state.keyword.trim()
? this.keywordFilter(lodash.cloneDeep(nodesData), this.state.keyword)
: nodesData;
return (
<div style={{marginLeft:"30px", marginTop:"30px", width:"30%"}}>
<div className="search">
<Input style={{marginBottom:"10px", width:"100%"}}
icon="fas fa-search"
placeholder="Search Categories"
iconPosition="left"
onChange={(event, data) => {
this.onSearchInputChange(event, data, searchedNodes);
}}
className="Change"
/>
</div>
<hr></hr>
<div className="checkbox-tree">
<CheckboxTree
nodes={searchedNodes}
checked={this.state.checked}
expanded={this.state.expanded}
onCheck={checked => this.setState({ checked })}
onExpand={expanded => this.setState({ expanded })}
expandOnClick
onClick = { () => { console.log("clicked"); }}
showNodeIcon={false}
icons={{
expandClose: <i class="fas fa-chevron-right fa-xs"></i>,
expandOpen: <i class="fas fa-chevron-down fa-xs"></i>,
}}
nameAsArray={true}
/>
</div>
<div>
<form class="butt">
<button type="button" onClick={this.cancel}>Cancel</button>
<button onClick={this.pushOnArray}>Apply</button>
</form>
</div>
</div>
)
}
Additionally I see you use some "advanced" stuff like cloneDeep while you do not understand basics of React. I recommend you to read the react docs, cuz answer I provided is the entry level concept of react.
New to react and I am removing the local state in my counter component and will be relying on the props to receive the data that it needs. I believe this is called a controlled component. After I got rid of the state and changed every where I was using this.state to this.props, I am no longer able to see the box that displays the value when I click my increment button. I will post all the code down below.
/* Counter Component*/
import React, { Component } from "react";
class Counter extends Component {
renderTags() {
return (
<ul>
{this.state.tags.length === 0 && <p> There are no tags </p>}
{this.state.tags.map(tag => (
<li key={tag}> {tag} </li>
))}
</ul>
);
}
// You can do styles this way or do it inline
// styles = {
// fontSize: 50,
// fontWeight: "bold"
// };
render() {
return (
<div>
<span style={{ fontSize: 20 }} className={this.getBadgeClasses()}>
{this.formatCount()}
</span>
<button
onClick={() => this.props.onIncrement(this.props.counter)}
className="btn btn-secondary btn-sm"
>
Increment
</button>
<button
onClick={() => this.props.onDelete(this.props.counter.id)}
className="btn btn-danger btn-sm m-2"
>
Delete
</button>
{/* {this.renderTags()}
<p>{this.state.tags.length === 0 && "Please create a new tag"}</p> */}
</div>
);
}
getBadgeClasses() {
let classes = "badge m-2 badge-";
classes += this.props.counter.value === 0 ? "warning" : "primary";
return classes;
}
formatCount() {
const { count } = this.props.counter;
return count === 0 ? "Zero" : count;
}
}
export default Counter;
/* Counters Component */
import React, { Component } from "react";
import Counter from "./counter";
class Counters extends Component {
state = {
counters: [
{ id: 1, value: 5 },
{ id: 2, value: 0 },
{ id: 3, value: 0 },
{ id: 4, value: 0 }
]
};
handleIncrement = counter => {
console.log(counter);
};
handleReset = () => {
const counters = this.state.counters.map(c => {
c.value = 0;
return c;
});
this.setState({ counters });
};
handleDelete = counterID => {
const counters = this.state.counters.filter(c => c.id !==
counterID);
this.setState({ counters });
};
render() {
return (
<React.Fragment>
<button onClick={this.handleReset} className="btn btn-dark btn-sm m-2">
Reset
</button>
{this.state.counters.map(counter => (
<Counter
key={counter.id}
onDelete={this.handleDelete}
counter={counter}
onIncrement={this.handleIncrement}
/>
))}
</React.Fragment>
);
}
}
export default Counters;
You can't see the values since you are using a wrong key for your counter.
formatCount() {
const { count } = this.props.counter;
return count === 0 ? "Zero" : count;
}
There isn't any key named count in your counter. It is value. So, you should use it or you need to destruct it like this:
const { value: count } = this.props.counter
But, using the same name is more consistent I think. Also, your Counter component would be a stateless one since you don't need any state or lifecycle method there.
One extra change would be done to the handler methods like onClick for onIncrement. If you use an arrow function, that function will be recreated in every render. You can use an extra handler method. Here is the complete working example (simplified for a clear view).
class Counters extends React.Component {
state = {
counters: [
{ id: 1, value: 5 },
{ id: 2, value: 0 },
{ id: 3, value: 0 },
{ id: 4, value: 0 }
]
};
handleIncrement = counter => {
const { counters } = this.state;
const newCounters = counters.map( el => {
if( el.id !== counter.id ) { return el; }
return { ...counter, value: counter.value + 1 }
} )
this.setState({ counters: newCounters});
};
handleReset = () => {
const counters = this.state.counters.map(c => {
c.value = 0;
return c;
});
this.setState({ counters });
};
handleDelete = counter => {
const { id: counterID } = counter;
const counters = this.state.counters.filter(c => c.id !== counterID);
this.setState({ counters });
};
render() {
return (
<div>
<button onClick={this.handleReset} className="btn btn-dark btn-sm m-2">
Reset
</button>
{this.state.counters.map(counter => (
<Counter
key={counter.id}
onDelete={this.handleDelete}
counter={counter}
onIncrement={this.handleIncrement}
/>
))}
</div>
);
}
}
const Counter = props => {
const { counter, onIncrement, onDelete} = props;
function formatCount(){
const { value } = counter;
return value === 0 ? "Zero" : value;
}
function handleIncrement(){
onIncrement( counter );
}
function handleDelete(){
onDelete( counter );
}
return (
<div>
<span>
{formatCount()}
</span>
<button
onClick={handleIncrement}
>
Increment
</button>
<button
onClick={handleDelete}
>
Delete
</button>
</div>
);
}
ReactDOM.render(<Counters />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>