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;
Related
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.
I am working on my react app specifically on removing one item at a time using my handleRemoveOption function:
handleRemoveOption(optionToRemove){
this.setState((prevState) => ({
options: prevState.options.filter((option) => optionToRemove !== option)
}));
}
Here's my full code:
class Box extends React.Component{
constructor(props){
super(props);
this.state = {
options: ['one', 'two', 'three']
}
this.handleRemoveAllOptions = this.handleRemoveAllOptions.bind(this);
this.handleDecision = this.handleDecision.bind(this);
this.handleAddOption = this.handleAddOption.bind(this);
this.handleRemoveOption = this.handleRemoveOption(this);
}
handleRemoveAllOptions(){
this.setState({
options: []
});
}
handleDecision(){
const randomNum = Math.floor(Math.random() * this.state.options.length);
const option = this.state.options[randomNum];
alert(option);
}
handleAddOption(option){
this.setState((prevState) => ({
options: prevState.options.concat(option)
}));
}
handleRemoveOption(optionToRemove){
this.setState((prevState) => ({
options: prevState.options.filter((option) => optionToRemove !== option)
}));
}
render(){
const title = 'Indecision app';
const subtitle = 'Put your life..';
return(
<div>
<Header
title={title}
subtitle={subtitle}
/>
<Action
handleDecision={this.handleDecision}
hasOptions={this.state.options.length === 0}
/>
<Options
options={this.state.options}
hasOptions={this.state.options.length === 0}
handleRemoveAllOptions={this.handleRemoveAllOptions}
handleRemoveOption={this.handleRemoveOption}
/>
<AddOption handleAddOption={this.handleAddOption} />
</div>
);
}
}
const Header = (props) => {
return(
<div>
<h1>{props.title}</h1>
<h3>{props.subtitle}</h3>
</div>
);
};
const Action = (props) => {
return(
<div>
<button
onClick={props.handleDecision}
disabled={props.hasOptions}>
Decide for me
</button>
</div>
);
};
const Options = (props) => {
return(
<div>
<button
onClick={props.handleRemoveAllOptions}
disabled={props.hasOptions}>
Remove All
</button>
<ol>
{ props.options.map((option) => (
<Option
key={option}
optionText={option}
handleRemoveOption={props.handleRemoveOption}
/>
))
}
</ol>
</div>
);
};
const Option = (props) => {
return (
<div>
<li>
<span>{ props.optionText }</span>
<button
onClick={(e) => {
props.handleRemoveOption(props.optionText);
}}>
Remove Option
</button>
</li>
</div>
);
};
class AddOption extends React.Component{
constructor(props){
super(props);
this.handleAddOption = this.handleAddOption.bind(this);
}
handleAddOption(e){
e.preventDefault();
const option = e.target.option.value.trim();
this.props.handleAddOption(option);
}
render(){
return(
<div>
<form onSubmit={this.handleAddOption}>
<input type="text" name="option" />
<button>Add Option</button>
</form>
</div>
);
}
}
ReactDOM.render(<Box />, document.getElementById('app'))
When I click on single button of each item it always say Uncaught TypeError: props.handleRemoveOption is not a function
What am I doing wrong here?
In your constructor of the Box component, all functions are bound to this, but handleRemoveOption is not.
Notice the missing this.handleRemoveOption.**bind**(this).
Editing line 13 to
this.handleRemoveOption = this.handleRemoveOption.bind(this);
will fix your issue!
I am currently creating a React todo application. So basically I have two component TodoList and TodoItem
TodoList component will receive an array of objects consisting title as the property and map through this with TodoItem component
In my TodoItem component, the user can choose to edit or delete the item. If the user chose to edit, a modal will show up with the existing title using a textarea. However, I have trouble implementing this function as the modal will always show up with the last element of the array.
TodoList Component
import React, { Component } from 'react'
import TodoItem from './TodoItem'
import { connect } from 'react-redux'
import { clear_todo } from '../store/actions/todoActions'
class Todolist extends Component {
clearList = (e) => {
e.preventDefault()
this.props.clearList(clear_todo());
}
handleChange = (index, title) => {
this.setState({
[index]: title
})
}
render() {
const { items, editItem } = this.props.todo
return (
<ul className="list-group my-5">
<h3 className="text-capitalize text-center">
Todo List
</h3>
{
items.map((item, index) => {
const editedTitle = item.title
return (<TodoItem key={item.id} title={item.title}
id={item.id}
editedTitle={editedTitle}
onChange={this.handleChange}
editItem={editItem} />)
})
}
<button className="btn btn-danger btn-block text-capitalize mt-5" onClick={this.clearList}> Clear List </button>
</ul>
)
}
}
const mapStateToProps = state => {
return {
todo: state.todo
}
}
const mapDispatchToProps = dispatch => {
return {
clearList: (clear_todo) => { dispatch(clear_todo) }
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Todolist)
TodoItem Component
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { delete_todo, edit_todo, toggle_edit } from '../store/actions/todoActions'
import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Form, FormGroup, Input } from 'reactstrap';
import TodoEditItem from './TodoEditItem'
class Todoitem extends Component {
// constructor(props) {
// super(props)
// this.state = {
// [props.id]: props.title
// }
// }
handleEdit = (id, title) => {
this.props.editTodo(edit_todo(id, title))
}
toggleEdit = (editItem, title) => {
this.props.toggleEdit(toggle_edit(!editItem))
// this.initializeTitle(title)
}
handleDelete = (id) => {
this.props.deleteTodo(delete_todo(id))
}
// onChange = (e, id) => {
// this.setState({
// [id]: e.target.value
// })
// }
componentDidMount() {
// console.log(this.props)
// this.initializeTitle(this.props.title)
this.setState({
[this.props.id]: this.props.editedTitle
})
console.log(this.state)
}
render() {
// console.log(this.state)
let { id, title, editItem, editedTitle, index } = this.props
console.log(id)
// console.log(index)
// let { item } = this.state
return (
<div>
<li className="list-group-item text-capitlize d-flex justify-content-between my-2">
<h6>{title}</h6>
<div className="todo-icon">
<span className="mx-2 text-success" onClick={this.toggleEdit.bind(this, editItem)} >
<i className="fas fa-pen"></i>
</span>
<span className="mx-2 text-danger" onClick={this.handleDelete.bind(this, id)}>
<i className="fas fa-trash"></i>
</span>
</div>
<Modal isOpen={editItem}>
<ModalHeader>Edit Todo Item</ModalHeader>
<ModalBody>
<Form>
<FormGroup row>
<Input type="textarea" name="text" value={this.state ? this.state[id] : ""} onChange={this.props.onChange} />
</FormGroup>
</Form>
</ModalBody>
<ModalFooter>
<Button color="primary" onClick={this.handleEdit.bind(this, id, editedTitle)}>Save</Button>{' '}
<Button color="secondary" onClick={this.toggleEdit.bind(this, editItem)}> Cancel</Button>
</ModalFooter>
</Modal>
</li>
{/* {editItem ? <TodoEditItem title={title} editItem={editItem} /> : ''} */}
</div>
)
}
}
const mapDispatchToProps = dispatch => {
return {
deleteTodo: (delete_todo) => { dispatch(delete_todo) },
editTodo: (edit_todo) => { dispatch(edit_todo) },
toggleEdit: (toggle_edit) => { dispatch(toggle_edit) },
}
}
export default connect(null, mapDispatchToProps)(Todoitem)
Sample Image
Image of TodoItem
Image of Modal
As you can see from the image of TodoItem, I am trying to edit "Take out the garbage" but my textarea has been prepopulated as the last element.
You're using the same variable to determine all of the TodoItem's open state. The result is it appears that it is only taking the last value from the array, but really each modal is opening at once and the last one is the only visible modal.
// editItem here is used to determine the open state of both modals
const { items, editItem } = this.props.todo
...
{
items.map((item, index) => {
const editedTitle = item.title
return (
<TodoItem
key={item.id}
title={item.title}
id={item.id}
editedTitle={editedTitle}
onChange={this.handleChange}
editItem={editItem} // Same value
/>
)
})
}
...
let { editItem } = this.props
<Modal isOpen={editItem}> // All will open and close at the same time
Instead, use a different flag, or just manage the open state in each child like this:
<span className="mx-2 text-success" onClick={() => this.setState({open: true})} >
<i className="fas fa-pen"></i>
</span>
...
<Modal isOpen={this.state.open}>
When I create a custom toolbar in the same file as the Calendar I can use the onView('day') method totally fine. It changes the views. However, when I put the same CalendarToolbar in a different file, and import it in the Calendar file it, doesn't update or change the view. I get the methods as props but it doesn't change anything.
// CustomToolbar
const CalendarToolbar = ({ onView, label, views }) => (
<div>
<h2>
{label}
</h2>
{views.map(view => (
<button
key={view}
type="button"
onClick={() => onView(view)}
>
{view}
</button>
))}
</div>
);
<Calendar
localizer={localizer}
defaultDate={new Date()}
defaultView="day"
events={mockEvents}
style={{ height: '100vh' }}
onSelectEvent={this.handleSelectEvent}
components={{
toolbar: CalendarToolbar,
}}
/>
Just wondered what I'm doing wrong?
I recently wrote my own custom Toolbar component. I copied the original Toolbar, from the repository, and then replaced the render() method with my own, copying what they had done and including my own stuff. My implementation stuff isn't completely important, but if you look at the onClick bits below, it may help you in doing what you want to do:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import ToolbarDateHeader from './ToolbarDateHeader.component';
import { Icon, Button, ButtonGroup, ButtonToolbar } from '../app';
const navigate = {
PREVIOUS: 'PREV',
NEXT: 'NEXT',
TODAY: 'TODAY',
DATE: 'DATE'
};
const propTypes = {
view: PropTypes.string.isRequired,
views: PropTypes.arrayOf(PropTypes.string).isRequired,
label: PropTypes.node.isRequired,
localizer: PropTypes.object,
onNavigate: PropTypes.func.isRequired,
onView: PropTypes.func.isRequired
};
export default class Toolbar extends Component {
static propTypes = propTypes;
render() {
let {
localizer: { messages },
label,
date
} = this.props;
return (
<ButtonToolbar>
<ButtonGroup>
<Button onClick={this.navigate.bind(null, navigate.TODAY)}>
{messages.today}
</Button>
<Button onClick={this.navigate.bind(null, navigate.PREVIOUS)}>
<Icon glyph="caret-left" />
</Button>
<Button onClick={this.navigate.bind(null, navigate.NEXT)}>
<Icon glyph="caret-right" />
</Button>
</ButtonGroup>
<ToolbarDateHeader date={date} onChange={this.toThisDay}>
{label}
</ToolbarDateHeader>
<ButtonGroup className="pull-right">
{this.viewNamesGroup(messages)}
</ButtonGroup>
</ButtonToolbar>
);
}
toThisDay = date => {
this.props.onView('day');
// give it just a tick to 'set' the view, prior to navigating to the proper date
setTimeout(() => {
this.props.onNavigate(navigate.DATE, date);
}, 100);
};
navigate = action => {
this.props.onNavigate(action);
};
view = view => {
this.props.onView(view);
};
viewNamesGroup(messages) {
let viewNames = this.props.views;
const view = this.props.view;
if (viewNames.length > 1) {
return viewNames.map(name => (
<Button
key={name}
className={cn({
active: view === name,
'btn-primary': view === name
})}
onClick={this.view.bind(null, name)}
>
{messages[name]}
</Button>
));
}
}
}
import React, { useState, useEffect } from "react";
import clsx from "clsx";
import moment from "moment";
function RBCToolbar(props) {
const { label, date, view, views, onView, onNavigate } = props;
const [month, setMonth] = useState("January");
const mMonth = moment(date).format("MMMM");
const months = moment.months();
useEffect(() => {
setMonth(mMonth);
}, [mMonth]);
const onChange = (event) => {
const current = event.target.value;
onNavigate("DATE", moment().month(current).toDate());
setMonth(current);
};
const goToView = (view) => {
onView(view);
};
const goToBack = () => {
onNavigate("PREV");
};
const goToNext = () => {
onNavigate("NEXT");
};
const goToToday = () => {
onNavigate("TODAY");
};
return (
<div className="rbc-toolbar">
<div className="rbc-btn-group">
<button onClick={goToToday}>Today</button>
<button onClick={goToBack}>Back</button>
<button onClick={goToNext}>Next</button>
</div>
<div className="rbc-toolbar-label">
{view === "month" ? (
<>
<select className="rbc-dropdown" value={month} onChange={onChange}>
{months?.map((month) => (
<option
value={month}
className="rbc-dropdown-option" //custom class
key={month}
>
{month}
</option>
))}
</select>
<span className="rbc-year"> {moment(date).format("YYYY")}</span>
</>
) : (
label
)}
</div>
<div className="rbc-btn-group">
{views?.map((item) => (
<button
onClick={() => goToView(item)}
key={item}
className={clsx({ "rbc-active": view === item })}
>
{item}
</button>
))}
</div>
</div>
);
}
export default RBCToolbar;
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 .