I am watching training videos on React by Mosh.
I have an Increment and a Decrement handler, and they are coded alike.
handleIncrement works, but handleDecrement does not.
There are no errors.
What am I doing wrong?
App.js
import React, { Component } from 'react';
import NavBar from './components/navbar';
import Counters from './components/counters';
import './App.css';
class App extends Component {
state = {
counters: [
{ id: 1, value: 4 },
{ id: 2, value: 0 },
{ id: 3, value: 0 },
{ id: 4, value: 0 },
{ id: 5, value: 0 },
]
};
constructor() {
super();
console.log("App - Constructor");
}
componentDidMount() {
// make calls to server
console.log("App - mounted!");
}
handleDelete = (counterId) => {
console.log('handle Delete: ', counterId);
const counters = this.state.counters.filter(x => x.id !== counterId );
this.setState({ counters });
};
handleDecrement = (counter) => {
const counters = [...this.state.counters];
const index = counters.indexOf(counter);
counters[index] = {...counter};
counters[index].value--;
this.setState(counters);
console.log('decrement counter', counter);
};
handleIncrement = (counter) => {
const counters = [...this.state.counters];
const index = counters.indexOf(counter);
counters[index] = {...counter};
counters[index].value++;
this.setState({counters});
console.log('increment counter', counter);
};
handleReset = () => {
console.log('reset counters');
const counters = this.state.counters.map(c => {
c.value = 0;
return c;
});
this.setState({counters});
};
render() {
console.log("App - Rendered");
return (
<>
<NavBar
totalCounters={this.state.counters.filter(x => 0 < x.value).length}
/>
<main role="main" className="container">
<Counters
counters={this.state.counters}
onDecrement={this.handleDecrement}
onDelete={this.handleDelete}
onIncrement={this.handleIncrement}
onReset={this.handleReset}
/>
</main>
</>
);
}
};
export default App;
Counter.jsx
import React, { Component } from 'react';
import 'bootstrap/dist/css/bootstrap.css';
class Counter extends Component {
componentDidUpdate(prevProps, prevState) {
console.log("prevProps", prevProps);
console.log("prevState", prevState);
}
componentWillUnmount() {
}
getBadgeClasses = () => {
let badge = (this.props.counter.value === 0) ? 'bg-warning' : 'bg-primary';
return 'badge ' + badge + ' m-2';
}
formatCount = () => {
const { value: count } = this.props.counter;
return count === 0 ? 'Zero' : count;
}
render() {
console.log("Counter - Rendered");
return (
<div className='row'>
<div className="col-1">
<span className={this.getBadgeClasses()}>{this.formatCount()}</span>
</div>
<div className="col">
<button
onClick={() => this.props.onIncrement(this.props.counter)}
className='btn btn-secondary btn-sm'>+</button>
<button
onClick={() => this.props.onDecrement(this.props.counter)}
className='btn btn-secondary btn-sm m-2'
disabled={this.props.counter.value === 0 ? 'disabled' : ''}>-</button>
<button
onClick={() => this.props.onDelete(this.props.counter.id)}
className="btn btn-danger btn-sm">x</button>
</div>
</div>
);
}
}
export default Counter;
Counters.jsx
import React, { Component } from 'react';
import Counter from './counter';
class Counters extends Component {
render() {
console.log("Counters - Rendered");
const { onReset, counters, onDecrement, onDelete, onIncrement } = this.props;
return (
<div>
<button
onClick={onReset}
className="btn btn-primary btn-sm m-2">Reset</button>
{counters.map(counter =>
<Counter
key={counter.id}
onDecrement={onDecrement}
onDelete={onDelete}
onIncrement={onIncrement}
counter={counter}
/>
)}
</div>
);
}
}
export default Counters;
On line 48 in App.js where you are setting state, you have wrapped counter with curly braces; on line 39 you have simply passed counter in the setState.
Related
I'm new to React and currently working on a to-do list app. Currently, I'm able to add, delete and edit the to-do list.
I have a problem filtering my to-do list based on categories. The categories I have are all, active and completed.
I'm stuck trying to filter the selected list based on the button clicked.
App.js
import React from "react";
import "./styles.css";
import "./App.css";
import Header from "./components/Header";
import AddTask from "./components/AddTask";
import Task from "./components/Task";
import Filterbtns from "./components/Filterbtns";
import data from "./data";
import { nanoid } from "nanoid";
const FILTER_MAP = {
All: () => true,
Active: (todo) => !todo.completed,
Completed: (todo) => todo.completed
};
const FILTER_NAMES = Object.keys(FILTER_MAP); //keys
function App() {
const [taskList, setTaskList] = React.useState(data);
const [filtered, setFiltered] = React.useState(data); //state to be filtered
const filteredListName = FILTER_NAMES;
const [activeList, setActiveList] = React.useState(filteredListName[0]); //default list
const taskItems = filtered.map((todo) => {
return (
<Task
id={todo.id}
name={todo.name}
completed={todo.completed}
key={todo.id}
toggleTaskCompleted={toggleTaskCompleted}
deleteTask={deleteTask}
editTask={editTask}
/>
);
});
const taskNoun = taskList.length !== 1 ? "tasks" : "task";
const headingText = `${taskList.length} ${taskNoun} remaining`;
function toggleTaskCompleted(id) {
const updatedTasks = taskList.map((todo) => {
if (id === todo.id) {
return { ...todo, completed: !todo.completed };
}
return todo;
});
setTaskList(updatedTasks);
}
function addTask(name) {
const newTask = { id: nanoid(), name: name, completed: false };
setTaskList([...taskList, newTask]);
}
function deleteTask(id) {
const remTasks = taskList.filter((todo) => id !== todo.id);
setTaskList(remTasks);
}
function editTask(id, newName) {
const editTaskList = taskList.map((todo) => {
if (id === todo.id) {
return { ...todo, name: newName };
}
return todo;
});
setTaskList(editTaskList);
}
return (
<div className="App">
<Header />
<AddTask addTask={addTask} />
<div>
<div className="task--list-btn">
<Filterbtns
taskList={taskList}
setFiltered={setFiltered}
filteredListName={filteredListName}
activeList={activeList}
setActiveList={setActiveList}
/>
<div className="task--lst">
<h2>TASKS</h2>
<h3>{headingText}</h3>
{taskItems}
</div>
</div>
<div>No task Available</div>
</div>
</div>
);
}
export default App
Filterbtns.js
import React from "react";
export default function Filterbtns(props) {
React.useEffect(() => {
if (props.activeList) {
props.setActiveList(props.filteredListName[0]);
console.log("try");
return;
}
const filtered = props.taskList.filter((todo) =>
todo.includes(props.activeList)
);
props.setFiltered(filtered);
}, [props.activeList]);
return (
<div className="task--btns">
<button
className="all-tasks inputs"
onClick={() => props.setActiveList(props.FilterbtnsfilteredListName[0])}
>
ALL
</button>
<br />
<button
className="active-tasks inputs"
onClick={() => props.setActiveList(props.filteredListName[1])}
>
ACTIVE
</button>
<br />
<button
className="completed-tasks inputs"
onClick={() => props.setActiveList(props.filteredListName[2])}
>
COMPLETED
</button>
</div>
);
}
I've not checked but from what it looks like React.useEffect is redundant inside Filterbtns and you need to pass down FilterbtnsfilteredListName to Filterbtns as props like this:
<Filterbtns
taskList={taskList}
setFiltered={setFiltered}
filteredListName={filteredListName}
activeList={activeList}
setActiveList={setActiveList}
FilterbtnsfilteredListName={filteredListName} // you forgot this
/>
Although if I can change the logic a bit, a better composition would be:
const FILTER_MAP = {
All: () => true,
Active: (todo) => !todo.completed,
Completed: (todo) => todo.completed
};
const FILTER_NAMES = Object.keys(FILTER_MAP); //keys
export default function App() {
const [taskList, setTaskList] = useState(data);
const [currentFilter, setCurrentFilter] = useState(FILTER_NAMES[0])
const filtered = taskList.filter(FILTER_MAP[currentFilter])
const taskItems = filtered.map((todo) => {
...
});
...
return (
<div className="App">
<Header />
<AddTask addTask={addTask} />
<div>
<div className="task--list-btn">
{/* IMPORTANT: FilterButton new API */}
<FilterButton
filterNames={FILTER_NAMES}
onFilter={setCurrentFilter}
/>
<div className="task--lst">
<h2>TASKS</h2>
<h3>{headingText}</h3>
{taskItems}
</div>
</div>
<div>No task Available</div>
</div>
</div>
);
}
function FilterButton(props) {
return (
<div className="task--btns">
{props.filterNames.map((filterName) => {
return <button
className={`${filterName}-tasks inputs`}
onClick={() => props.onFilter(filterName)}
>
{filterName}
</button>
})}
</div>
)
}
Happy React journey! you are doing great.
trying to send id property from counter to counters, so that I can delete the respective React element.
*in counters component's handleDelete trying to see the counterid, but getting an undefined response, just can i get some help to know why it is undefined, where i believe to get 1,2,3,4,5.
counter.jsx
import React, { Component } from "react";
class Counter extends Component {
state = {
value: this.props.value,
};
handleIncrement = () => {
this.setState({ value: this.state.value + 1 });
};
render() {
return (
<div>
<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>
);
}
getBadgeClasses() {
let classes = "badge m-2 badge-";
classes += this.state.value === 0 ? "warning" : "primary";
return classes;
}
formatCount() {
const { value: count } = this.state;
return count === 0 ? "Zero" : count;
}
}
export default Counter;
counters.jsx
import React, { Component } from "react";
import Counter from "./counter";
class Counters extends Component {
state = {
counters: [
{ id: 1, value: 0 },
{ id: 2, value: 4 },
{ id: 3, value: 3 },
{ id: 4, value: 2 },
{ id: 5, value: 15 },
],
};
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}
></Counter>
))}
</div>
);
}
}
export default Counters;
You are not passing any id to the Counter component. The key attribute is only used to identify the component through the app and avoid collisions when you call more than once the same component.
What you have to do is send an id prop to your Counter component in Counters.jsx:
this.state.counters.map((counter) => (
<Counter
key={counter.id}
onDelete={this.handleDelete}
value={counter.value}
id={counter.id} // HERE: send an id prop to your component
></Counter>
))
In this way your component Counter will be able to call this.props.id.
iam new to react i tried to fix it by using bind method on my other projects. then i heard that binding is not required while using arrow function. so i now trying to use arrow function but getting this error all the time
import React, { Component } from "react";
class Counter extends Component {
render() {
return (
<div>
<span className={this.getBadgeClasses()}>{this.formatCount()}</span>
<button onClick={()=>this.props.handleIncrement(this.props.counter)}
className="btn btn-secondary btn-sm m-2 p-3">Increment</button>
<button onClick={()=>this.props.onDelete(this.props.id)} className="btn btn-danger btm-sm">Delete</button>
</div>
);
}
formatCount(){
const { value } = this.props.counter;
return value === 0 ? "Zero" : value;
}
getBadgeClasses(){
let classes="badge p-3 badge-";
classes+= this.props.counter.value===0 ? "warning":"primary";
return classes
}
}
export default Counter;
i have imported counter into this counters.jsx.
import React, { Component } from 'react';
import Counter from "./counter"
class Counters extends Component {
state = {
counters :[
{id:1 , value:2},
{id:2 , value:0},
{id:3 , value:4},
{id:4 , value:0},
{id:5 , value:5}
]
}
handleIncrement=counter=>{
console.log(counter);
}
handleReset=()=>{
}
handleDelete=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}
onIncrement={this.handleIncrement} value={counter.value} id={counter.id} />
)}
</div>
);
}
}
export default Counters;
while compiling iam getting the error TypeError: Cannot read property 'value' of undefined
. i used arrow function instead of binding the function enter image description here
You're sending the prop as value but in the child component you're accessing it as counter.value.
this.props.counter.value should be this.props.value. Or else send the counter to the child.
Issues
counter.value is passed to value prop of Counter, but accessed as this.props.counter.value.
this.handleIncrement is passed to onIncrement prop, but accessed as this.props.handleIncrement.
this.props.handleIncrement passes this.props.counter as argument, but both are undefined.
Solutions
Access correctly this.props.value.
formatCount() {
const { value } = this.props; // <-- destructure correctly
return value === 0 ? "Zero" : value;
}
getBadgeClasses() {
let classes = "badge p-3 badge-";
classes += this.props.value === 0 ? "warning" : "primary"; // <-- access correctly
return classes;
}
I suggest converting your handlers in Counters to curried functions so you don't need to pass the id explicitly. I also suggest using functional state updates so counts are correctly updated from the previous state.
handleIncrement = (id) => () => {
this.setState((prevState) => ({
counters: prevState.counters.map((counter) =>
counter.id === id
? {
...counter,
value: counter.value + 1
}
: counter
)
}));
};
handleDelete = (id) => () => {
this.setState((prevState) => ({
counters: prevState.counters.filter((counter) => counter.id !== id)
}));
};
Attach the modified handlers and access accordingly. Here we will pass the id value to the curried handler.
Counters
<div>
{this.state.counters.map((counter) => (
<Counter
key={counter.id}
onDelete={this.handleDelete(counter.id)} // <-- pass id
onIncrement={this.handleIncrement(counter.id)} // <-- pass id
value={counter.value}
/>
))}
</div>
Counter
<div>
...
<button
onClick={this.props.onIncrement} // <-- attach handler callback
className="btn btn-secondary btn-sm m-2 p-3"
>
Increment
</button>
<button
onClick={this.props.onDelete} // <-- attach handler callback
className="btn btn-danger btm-sm"
>
Delete
</button>
</div>
Full Code
class Counter extends Component {
render() {
const { onDelete, onIncrement, value } = this.props;
return (
<div>
<span className={this.getBadgeClasses()}>{value}</span>
<button
onClick={onIncrement}
className="btn btn-secondary btn-sm m-2 p-3"
>
Increment
</button>
<button onClick={onDelete} className="btn btn-danger btm-sm">
Delete
</button>
</div>
);
}
getBadgeClasses() {
const { value } = this.props;
let classes = "badge p-3 badge-";
classes += value ? "primary" : "warning";
return classes;
}
}
class Counters extends Component {
state = {
counters: [
{ id: 1, value: 2 },
{ id: 2, value: 0 },
{ id: 3, value: 4 },
{ id: 4, value: 0 },
{ id: 5, value: 5 }
]
};
handleIncrement = (id) => () => {
this.setState((prevState) => ({
counters: prevState.counters.map((counter) =>
counter.id === id
? {
...counter,
value: counter.value + 1
}
: counter
)
}));
};
handleReset = () => {};
handleDelete = (id) => () => {
this.setState((prevState) => ({
counters: prevState.counters.filter((counter) => counter.id !== id)
}));
};
render() {
const { counters } = this.state;
return (
<div>
{counters.map(({ id, value }) => (
<Counter
key={id}
onDelete={this.handleDelete(id)}
onIncrement={this.handleIncrement(id)}
value={value || "Zero"}
/>
))}
</div>
);
}
}
I have a simple counter that I render an Items component that gets props and state as props. I want multiple renders of that same component to use just one counter state instead of counter1 counter2 etc...
Also I want to have just increase and decrease function instead of multiple. Hope the code explains what im trying to say. Is there anyway to do this?
import React from "react";
import Navbar from "./NavBar";
import Items from "./Items";
import "./App.css";
class App extends React.Component {
constructor(props) {
super();
this.state = {
counter1: 0,
counter2: 0,
counter3: 0,
};
}
render() {
//Counter 1
const Increase1 = () => {
this.setState({ counter1: this.state.counter1 + 1 });
};
const Decrease1 = () => {
if (this.state.counter > 0) {
this.setState({ counter1: this.state.counter1 - 1 });
}
};
//Counter 2
const Increase2 = () => {
this.setState({ counter2: this.state.counter2 + 1 });
};
const Decrease2 = () => {
if (this.state.counter > 0) {
this.setState({ counter2: this.state.counter2 - 1 });
}
};
//Counter 3
const Increase3 = () => {
this.setState({ counter3: this.state.counter3 + 1 });
};
const Decrease3 = () => {
if (this.state.counter > 0) {
this.setState({ counter3: this.state.counter3 - 1 });
}
};
return (
<div>
<Navbar />
<div className="container mt-4">
<button className="btn btn-primary mr-4">Redo</button>
<button
className="btn btn-secondary"
onClick={() => window.location.reload()}
>
Reload
</button>
<Items
amount={this.state.counter1}
increase={Increase1}
decrease={Decrease1}
/>
<Items
amount={this.state.counter2}
increase={Increase2}
decrease={Decrease2}
/>
<Items
amount={this.state.counter3}
increase={Increase3}
decrease={Decrease3}
/>
</div>
</div>
);
}
}
export default App;
And here is the Items Component
import React from "react";
function Items(props) {
console.log(props);
return (
<div>
<h6 className="mt-3">{props.amount}</h6>
<button className="btn btn-success mr-3" onClick={props.increase}>
+
</button>
<button className="btn btn-info mr-3" onClick={props.decrease}>
-
</button>
<button className="btn btn-danger mr-3">Delete</button>
</div>
);
}
export default Items;
Sorry for the confusing code it was weird to get into a code block
About the idea you can use only one increase and decrease function for multiple Items by specific a key for each Item
import React from 'react';
import Navbar from './NavBar';
import Items from './Items';
import './App.css';
class App extends React.Component {
constructor(props) {
super();
this.state = {
counter1: 0,
counter2: 0,
counter3: 0,
};
}
increase = (key) => {
this.setState((prevState) => ({ ...prevState, [key]: prevState[key] + 1 }));
};
decrease = (key) => {
this.setState((prevState) => ({ ...prevState, [key]: prevState[key] - 1 || 0}));
};
render() {
return (
<div>
<Navbar />
<div className="container mt-4">
<button className="btn btn-primary mr-4">Redo</button>
<button
className="btn btn-secondary"
onClick={() => window.location.reload()}
>
Reload
</button>
<Items
amount={this.state.counter1}
increase={() => this.increase('counter1')}
decrease={() => this.decrease('counter1')}
/>
<Items
amount={this.state.counter2}
increase={() => this.increase('counter2')}
decrease={() => this.decrease('counter2')}
/>
<Items
amount={this.state.counter3}
increase={() => this.increase('counter3')}
decrease={() => this.decrease('counter3')}
/>
</div>
</div>
);
}
}
export default App;
I have two componenets , counter and counters. I have a box that shows the value when you click the increment button in my counter component thats not being displayed. I refactored my code so that that my counter component is a controlled component instead of an uncontrolled component so it gets its data from my props object. I will paste the code down below.
Update: I am now able to see the box that has the number of increments but when i click Increment I get Nan displayed in the box for the value.
counter component
import React, { Component } from "react";
class Counter extends Component {
// styles for our bootstrap
styles = {
fontSize: 30,
fontWeight: "bold"
};
render() {
console.log("props", this.props);
return (
<div>
<span className={this.getBadgeColor()}>{this.formatCount()}
</span>
<button
onClick={() => this.props.onIncrement(this.props.counter)}
className="btn btn-secondary btn-md"
>
Increment
</button>
<button
onClick={() => this.props.onDelete(this.props.counter.id)}
className="btn btn-danger btn-sm m-2"
>
Delete
</button>
</div>
);
}
getBadgeColor() {
let classes = "badge m-2 badge-";
classes += this.props.counter.value === 0 ? "warning" :
"primary";
return classes;
}
formatCount() {
const { value } = this.props.counter;
return value === 0 ? <h2> Zero </h2> : value;
}
}
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 }
]
};
handleDelete = counterId => {
const counters = this.state.counters.filter(c => c.id !==
counterId);
this.setState({ counters });
};
handleReset = () => {
const counters = this.state.counters.map(c => {
c.value = 0;
return c;
});
this.setState.counters = { counters };
};
handleIncrement = counter => {
const counters = [...this.state.counters];
const index = counters.indexOf(counter);
counters[index] = { ...counters };
counters[index].value++;
this.setState({ counters });
};
render() {
return (
<div>
<button
onClick={this.handleReset}
className="btn btn-primary btn-sm m-2"
>
Reset
</button>
{this.state.counters.map(counters => (
<Counter
key={counters.id}
onDelete={this.handleDelete}
counter={counters}
onIncrement={this.handleIncrement}
/>
))}
</div>
);
}
}
export default Counters;
you are seeing NaN because in the counters component you should assign values of state .