With the help of a user here I was able to make a component, however now I need a variation of that component. The component is an increment / decrement button with some details:
1 - The button is in an array coming from an API
2 - The increment / decrement must follow a rule defined in the bank, that is, a maximum number of items
3 - A footer with the Buy button should appear automatically when one of the items exists
4 - When I click the Buy button, the items will be saved in the localStorage and a new screen will open, called a shopping cart that will contain the same increment / decrement buttons but with the items saved in the localStorage selected in the list and the buttons should increment / decrement the items and clicking the Confirm Purchase button the localStorage must be updated.
Until item 3 and half of item 4 I was able to do, but I'm having problem with the increment / decrement buttons. Below is the code for my components
//Change Quantity Button
import React from 'react';
import './ChooseQuantity.css';
const ChooseQuantity = ({ value, onChange, additionEnabled }) => {
const shouldIncrement = additionEnabled;
const shouldDecrement = value > 0;
const decrement = () => {
if (shouldDecrement) {
onChange(value - 1);
}
}
const increment = () => {
if (shouldIncrement) {
onChange(value + 1);
}
}
const decrementButton = shouldDecrement ? (
<button className="minus" onClick={decrement}>
<i className="fas fa-minus"></i>
</button>
) : <div className="space-button"></div>
const incrementButton = shouldIncrement ? (
<button className='plus' onClick={increment}>
<i className="fas fa-plus"></i>
</button>
) : <div className="space-button"></div>
return (
<div>
{decrementButton}
<span className="qtd">{value}</span>
{incrementButton}
</div>
)
}
ChooseQuantity.defaultProps = {
value: 0,
additionEnabled: true,
}
export default ChooseQuantity
//Lot Component
import React from 'react'
import ChooseQuantity from '../../components/ChooseQuantity/ChooseQuantity.js';
const Lot = ({
ticketName,
ticketPrevenda,
lotUniqueNumber,
ticketUniqueNumber,
name,
lot,
lotType,
lotNumber,
lotPrice,
lotPriceTax,
lotQuantity,
onQuantityChange,
additionEnabled,
maxPurchase,
separator = '/',
quantity,
newValue,
}) => {
const onQuantityChangeInternal = (newValue) => {
onQuantityChange(ticketName, ticketPrevenda, ticketUniqueNumber, lotType, lotNumber, lotUniqueNumber, lotPrice, lotPriceTax, newValue, lotQuantity, maxPurchase)
}
return (
<div className="row">
<div className="col-8">
<h5 className="lot-name">{name}</h5>
<h5 className="lot-name">
{
lotType === 'U' ? 'Unissex ' : ''
}
{
lotType === 'M' ? 'Masculino ' : ''
}
{
lotType === 'F' ? 'Feminino ' : ''
}
({lotNumber}º Lote)
</h5>
<h6 className="lot-price">
R$ {lotPrice.replace('.', ',')} <br />
<small>(R$ {lotPrice.replace('.', ',')} + R$ {lotPriceTax.replace('.', ',')})</small>
</h6>
</div>
<div className="col-4">
<ChooseQuantity
value={lotQuantity}
onChange={onQuantityChangeInternal}
additionEnabled={additionEnabled}
maxPurchase={maxPurchase}
lot={lot}
lotPrice={lotPrice}
lotPriceTax={lotPriceTax}
lotQuantity={lotQuantity}
newValue={newValue}
/>
</div>
</div>
)
}
export default Lot
import React, { Component } from 'react';
import Lot from '../Cart/Lot';
import './Cart.css';
import '../../components/Css/App.css';
import Header from '../../components/Header/Header';
const separator = '/';
class Cart extends Component {
constructor(props) {
super(props);
this.state = {
cart: [],
lot: [],
qtd: 0,
events: null,
banner_app: null,
event_name: null,
priceTotal: null,
quantity: null,
selectedQuantities: {},
maxTotalItems: 0,
}
}
async componentDidMount() {
const cart = JSON.parse(localStorage.getItem('cart'));
await this.setState({ cart: cart });
this.setState({ events: this.state.cart.events });
this.setState({ banner_app: this.state.cart.events.banner_app });
this.setState({ event_name: this.state.cart.events.name });
this.setState({ priceTotal: this.state.cart.total.price });
this.setState({ quantity: this.state.cart.total.totalQuantity });
this.setState({ lot: this.state.cart.events.tickets.lot });
this.setState({ maxTotalItems: this.state.events.max_purchase });
this.setState({ selectedLots: this.state.cart.events.tickets.lot });
}
onQuantityChange = (ticketName, ticketPrevenda, ticketUniqueNumber, lotType, lotNumber, lotUniqueNumber, lotPrice, lotPriceTax, newValue, lotQuantity) => {
// console.log(lotQuantity);
this.setState(prevState => {
this.setState({
selectedQuantities: { ...prevState.selectedQuantities, [`${ticketName}${separator}${ticketPrevenda}${separator}${ticketUniqueNumber}${separator}${lotType}${separator}${lotNumber}${separator}${lotUniqueNumber}${separator}${lotPrice}${separator}${lotPriceTax}`]: newValue },
})
}, () => {
const selectedArray = Object.entries(this.state.selectedQuantities).map(
([key, quantity]) => {
const [ticketName, ticketPrevenda, ticketUniqueNumber, lotType, lotNumber, lotUniqueNumber, lotPrice, lotPriceTax] = key.split(separator)
const totalLotPrice = parseFloat(lotPrice + lotPriceTax);
const total = parseFloat(totalLotPrice * quantity);
return {
ticketName,
ticketPrevenda,
ticketUniqueNumber,
lotType,
lotNumber,
lotUniqueNumber,
lotPrice,
lotPriceTax,
quantity,
totalLotPrice,
total
}
},
)
// console.log(selectedArray);
// console.log(this.state.quantity);
//SOMANDO A QTD E ATUALIZANDO O STATE
// var lotQuantity = selectedArray.reduce(function(prevVal, elem) {
// console.log(elem.quantity)
// const lotQuantity = prevVal + elem.quantity;
// return lotQuantity;
// }, 0);
// this.setState({ qtd: lotQuantity });
// console.log(this.state.lotQuantity);
// //SOMANDO O TOTAL E ATUIALIZANDO O STATE
// var total = selectedArray.reduce(function(prevVal, elem) {
// const total = prevVal + elem.total;
// return total;
// }, 0);
// this.setState({priceTotal: total})
// //MOSTRAR/OCULTAR FOOTER
// if (lotQuantity > 0) {
// this.setState({ totalZero: true });
// } else {
// this.setState({ totalZero: false });
// }
// //OBJETO CART
// var lot = selectedArray;
// var tickets = {
// name: ticketName,
// prevenda: ticketPrevenda,
// unique_number: ticketUniqueNumber,
// lot: lot
// }
// total = {
// price: total,
// quantity: lotQuantity,
// };
// var events = {
// banner_app: this.state.event.banner_app,
// installments: this.state.event.installments,
// max_purchase: this.state.event.max_purchase,
// name: this.state.event.name,
// tickets: tickets,
// unique_number: this.state.event.unique_number,
// total_tickets: lotQuantity
// };
// var cart = { events: events, total: total };
// localStorage.setItem('cart', JSON.stringify(cart));
// localStorage.setItem('qtd', JSON.stringify(lotQuantity));
// console.log(lotQuantity);
// console.log(JSON.parse(localStorage.getItem('cart')));
//OBJETO CART
})
}
// getSelectedQuantity = (ticketName, ticketPrevenda, ticketUniqueNumber, lotType, lotNumber, lotUniqueNumber, lotPrice, lotPriceTax) => this.state.selectedQuantities[`${ticketName}${separator}${ticketPrevenda}${separator}${ticketUniqueNumber}${separator}${lotType}${separator}${lotNumber}${separator}${lotUniqueNumber}${separator}${lotPrice}${separator}${lotPriceTax}`];
//HERE IS THE FUNCTION THAT INCREMENT/DECREMENT ITEMS
getSelectedQuantity = (ticketName, ticketPrevenda, ticketUniqueNumber, lotType, lotNumber, lotUniqueNumber, lotPrice, lotPriceTax, lotQuantity) => {
// console.log(lotQuantity);
// console.log(this.state.selectedQuantities);
// lot.reduce(function(prevVal, elem) {
// // const soma = parseInt(elem) + parseInt(lotQuantity);
// console.log(elem)
// // return soma;
// }, 0);
// console.log(myLotQuantity);
// return myLotQuantity;
// console.log(this.state.selectedQuantities);
return this.state.selectedQuantities[
`${ticketName}${separator}${ticketPrevenda}${separator}${ticketUniqueNumber}${separator}${lotType}${separator}${lotNumber}${separator}${lotUniqueNumber}${separator}${lotPrice}${separator}${lotPriceTax}`
];
// return lotQuantity;
}
getAdditionEnabled = () => Object.values(this.state.selectedQuantities).reduce((acc, i) => acc + i, 0) < this.state.maxTotalItems;
onCheckoutButtonClick = () => {
const selectedArray = Object.entries(this.state.selectedQuantities).map(
([key, quantity]) => {
const [ticketName, ticketPrevenda, ticketUniqueNumber, lotType, lotNumber, lotUniqueNumber, lotPrice, lotPriceTax] = key.split(separator)
const totalLotPrice = parseFloat(lotPrice + lotPriceTax);
const total = parseFloat(totalLotPrice * quantity);
return {
ticketName,
ticketPrevenda,
ticketUniqueNumber,
lotType,
lotNumber,
lotUniqueNumber,
lotPrice,
lotPriceTax,
quantity,
totalLotPrice,
total
}
},
)
console.log(selectedArray);
}
render() {
return (
<div>
<Header Title="Carrinho" ToPage="/" />
<div className="cart">
<div className="container-fluid">
<div className="box-price">
<div className="row box-default ">
<div className="col col-price">
<h6>{this.state.quantity} INGRESSO{this.state.quantity > 1 ? 'S' : ''}</h6>
<h5>R$ {parseFloat(this.state.priceTotal).toFixed(2).replace('.', ',')}</h5>
</div>
</div>
</div>
<div className="box-default">
<div className="row no-margin">
<div className="col-12 col-image no-padding">
<img src={this.state.banner_app} alt="" />
</div>
<div className="col-12 no-padding">
<h5 className="event-name">{this.state.event_name}</h5>
</div>
</div>
{
this.state.lot.map((lot, l) =>
// <div className="row" key={l}>
// <div className="col-8">
// <h5 className="lot-name">{lot.name}</h5>
// <h5 className="lot-name">
// {
// lot.lotType === 'U' ? 'Unissex ' : ''
// }
// {
// lot.lotType === 'M' ? 'Masculino ' : ''
// }
// {
// lot.lotType === 'F' ? 'Feminino ' : ''
// }
// ({lot.lotNumber}º Lote)
// </h5>
// <h6 className="lot-price">
// R$ {lot.lotPrice.replace('.', ',')} <br />
// <small>(R$ {lot.lotPrice.replace('.', ',')} + R$ {lot.lotPriceTax.replace('.', ',')})</small>
// </h6>
// </div>
// <div className="col-4">
// <ChooseQuantity
// value={this.getSelectedQuantity(lot.quantity)}
// onChange={this.onQuantityChange}
// additionEnabled={this.additionEnabled}
// maxPurchase={this.state.events.max_purchase}
// lotPrice={lot.lotPrice}
// lotPriceTax={lot.lotPriceTax}
// lotQuantity={lot.lotQuantity}
// />
// </div>
// <div className="col-12">
// {this.state.lot.length > 1 ? <hr /> : ''}
// </div>
// </div>
<div key={l}>
<Lot
events={this.state.events}
ticketName={this.state.cart.events.tickets.name}
ticketPrevenda={this.state.cart.events.tickets.prevenda}
ticketUniqueNumber={this.state.cart.events.tickets.unique_number}
lotUniqueNumber={lot.lotUniqueNumber}
lotName={lot.name}
lotType={lot.lotType}
lotNumber={lot.lotNumber}
lotPrice={lot.lotPrice}
lotPriceTax={lot.lotPriceTax}
onQuantityChange={this.onQuantityChange}
maxPurchase={this.state.events.max_purchase}
lotQuantity={this.getSelectedQuantity(this.state.cart.events.tickets.name, this.state.cart.events.tickets.prevenda, this.state.cart.events.tickets.unique_number, lot.lotType, lot.lotNumber, lot.lotUniqueNumber, lot.lotPrice, lot.lotPriceTax, lot.quantity)}
// quantity={lot.quantity}
additionEnabled={this.getAdditionEnabled()}
/>
{this.state.lot.length > 1 ? <hr /> : ''}
</div>
)
}
<div className="row cart-footer" style={{ marginRight: '-15px', marginLeft: '-15px', backgroundColor: '#f4f7fa' }}>
<button className="col col-purchase" to="/cart" style={{ justifyContent: 'center', alignItems: 'center' }} onClick={this.onCheckoutButtonClick}>
Confirmar Comprar
</button>
</div>
</div>
</div>
</div>
</div>
);
}
}
export default Cart;
In the getSelectedQuantity function if I put
return this.state.selectedQuantities[
`${ticketName}${separator}${ticketPrevenda}${separator}${ticketUniqueNumber}${separator}${lotType}${separator}${lotNumber}${separator}${lotUniqueNumber}${separator}${lotPrice}${separator}${lotPriceTax}`
];
It updates the items on the increment / decrement button, but I need it to show the quantity of items that exist in the localStorage and update the quantity. I've been trying for a few days and nothing
This might not be a complete answer for your problem, but you're causing a bloody hell in your componentDidMount, especially by calling this.setState() so many times. Remember a thing - each time you call this.setState you're re-rendering the component.
Also, this.setState() doesn't return a promise, so await this.setState({...}) doesn't make any sense.
So, the first improvement for your component will be:
componentDidMount() {
const cart = JSON.parse(localStorage.getItem('cart'));
const {
events,
events: { tickets },
total
} = cart;
this.setState({
cart,
events,
banner_app: events.banner_app,
event_name: events.event_name,
priceTotal: total.price,
quantity: total.totalQuantity,
lot: tickets.lot,
maxTotalItems: events.max_purchase,
selectedLots: tickets.lot,
});
}
Related
I am looking for feedback regarding this React code I am writing. I previously posted when I first started out on this and I have since iterated on it and tried to implement best practices. I am learning a lot and I cannot help, but feel that my current code is clunky. In what ways can I improve the design?
I am having a hard time figuring out what needs to be a component, what needs to be a function, and what can just be a value (even then whether it should be state). I also feel like I am misusing variable scope, but I can't put my finger on how. One specific thing I cannot resolve is ESlint is highlighting mostly everything in my file with this error "Function component is not a function declaration". Any and all feedback is appreciated. Thank you!
/* eslint-disable react/prop-types */
/* eslint-disable no-underscore-dangle */
import axios from 'axios';
import React, { useState, useEffect, useCallback, useRef } from 'react';
import { GetWishes, InsertWish } from './apis';
// Main component, acts a wrapper for the entire screen content
const Wishlist = () => {
const [loading, setLoading] = useState('initial');
const [listOfWishes, setListOfWishes] = useState('default');
// Passed down to update the main list state
function updateListOfWishes(list) {
setListOfWishes([...list]);
}
// Sorting lists dynamically
function dynamicSort(property) {
let sortOrder = 1;
let newprop = '';
if (property[0] === '-') {
sortOrder = -1;
newprop = property.substr(1);
}
return (a, b) => {
let result = 0;
if ((a[newprop] < b[newprop])) {
result = -1;
} else if (a[newprop] > b[newprop]) {
result = 1;
}
return result * sortOrder;
};
}
// Get all items from DB, this is main list
async function GetWishesList() {
try {
const apiresp = await GetWishes();
apiresp.sort(dynamicSort('-source'));
const goodlist = apiresp.map((item) => ({ ...item, isReadOnly: true, show: true }));
setListOfWishes(goodlist);
setLoading('false');
} catch (e) {
console.log(`Error in Wishlist.GetWishesList: ${e.message}`);
}
}
// Only once, get items and set loading state
useEffect(() => {
setLoading('true');
GetWishesList();
}, []);
if (loading === 'initial') {
return <h2 className="content">Initializing...</h2>;
}
if (loading === 'true') {
return <h2 className="content">Loading...</h2>;
}
// Return header and content, pass down function for deep state update
return (
<div className="contentwrapper">
<WishlistHeader fullList={listOfWishes} updateListOfWishes={updateListOfWishes} />
<WishTable fullList={listOfWishes} updateListOfWishes={updateListOfWishes} />
</div>
);
}
// Header component
const WishlistHeader = (props) => {
const [filter, setFilter] = useState('all');
let list = props.fullList;
// Get length of current filtered list
function getShowCount(list) {
return list.filter((item) => item.show === true).length;
}
// Update shown list items when filter changes
const HandleFilterChange = (e) => {
const { fullList, updateListOfWishes } = props;
const { value } = e.target;
for (let i = fullList.length - 1; i >= 0; i -= 1) {
fullList[i].isReadOnly = true;
fullList[i].show = true;
if (value !== 'all' && fullList[i].category !== value) {
fullList[i].show = false;
}
}
setFilter(value);
const newlist = fullList.map(i => {
return { ...i };
});
updateListOfWishes(newlist);
}
// Return header component content
return (
<div className="contentBanner">
<h1 className="wishTitle">
Wishes:
{' '}
{getShowCount(list)}
</h1>
<label htmlFor="category">
<p className="bannerFilter">Category</p>
<select id="category" name="category" value={filter} onChange={(e) => HandleFilterChange(e)}>
<option value="all">All</option>
<option value="default">Default</option>
<option value="camping">Camping</option>
<option value="hendrix">Hendrix</option>
<option value="decor">Decor</option>
</select>
</label>
</div>
);
}
// Component to show list of items
function WishTable(props) {
const rows = [];
let { fullList, updateListOfWishes } = props;
if (fullList === null) {
console.log('currentList is null');
} else {
fullList.forEach(function (item) {
rows.push(
<div key={item._id} >
<WishRow item={item} currentList={fullList} updateListOfWishes={updateListOfWishes} />
</div>)
})
}
return (
<div className="content">
{rows}
</div>
);
};
// Individual row render for each item
const WishRow = (props) => {
let item = props.item;
let prevItem = useRef(item);
// Store unedited item in case of cancel, mark not read only
const handleEdit = () => {
let { item, currentList, updateListOfWishes } = props;
prevItem.current = { ...item };
const newlist = currentList.map(i => {
if (i._id === item._id) {
return { ...i, isReadOnly: false }
}
return { ...i };
});
updateListOfWishes(newlist);
};
// Send item to DB
async function insertWish(item) {
try {
await InsertWish(item);
} catch (e) {
console.log(`Error in wishlist.insertWish: ${e.message}`);
}
};
// Send current item info to DB and mark read only
const handleSubmit = () => {
let { item, currentList, updateListOfWishes } = props;
insertWish(item);
const newlist = currentList.map(i => {
if (i._id === item._id) {
return { ...i, isReadOnly: true };
}
return { ...i };
});
updateListOfWishes(newlist);
};
// Return content for submit button
function Submit(item) {
if (!item.isReadOnly) {
return (
<span>
<button className="typicalbutton" type="button" onClick={() => handleSubmit(item)}>
Submit
</button>
</span>
);
}
return null;
};
// Return content for edit button
function ShowEdit(item) {
if (item.source === 'manual') {
return (
<span >
<button className="typicalbutton righthand" type="button" onClick={() => handleEdit(item, props.currentList)}>
Edit
</button>
</span>
);
}
return null;
};
// Revert to unedited item and mark read only
const handleCancel = () => {
let { item, currentList, updateListOfWishes } = props;
const newlist = currentList.map(i => {
if (i._id === item._id) {
return { ...prevItem.current, isReadOnly: true }
}
return { ...i };
});
updateListOfWishes(newlist);
};
// Return content for cancel button
function Cancel(item) {
if (!item.isReadOnly) {
return (
<span>
<button className="typicalbutton" type="button" onClick={(e) => handleCancel(e, prevItem, item)}>
Cancel
</button>
</span>
);
}
return null;
};
// Update item when fields edited
const handleChange = (e) => {
let { item, currentList, updateListOfWishes } = props;
const { name, value } = e.target;
item[name] = value;
const newlist = currentList.map(i => {
return { ...i };
});
updateListOfWishes(newlist);
};
// Update item for category change
const handleCategoryChange = (e) => {
const { value } = e.target;
let { item, currentList, updateListOfWishes } = props;
item.category = value;
const newlist = currentList.map(i => {
return { ...i };
});
updateListOfWishes(newlist);
};
// Open url in new tab
const openInTab = (url) => {
const newWindow = window.open(url, '_blank', 'noopener,noreferrer');
if (newWindow) newWindow.opener = null;
};
// Get the url from backend
async function getUrl(link) {
const toflask = `/go_outside_flask/${link}`;
const tempurl = await axios.get(toflask);
const newurl = tempurl.data;
return newurl;
}
// Get the url from backend and go to it in new tab
const goToLink = async (link) => {
let taburl = '';
try {
taburl = await getUrl(link);
return openInTab(taburl);
} catch (e) {
console.log(`Error getting url from link: ${link} ${e.message}`);
return window.location.href;
}
};
// Row content, if read only show just fields, if not read only then show different buttons and editable fields
return (
<div >
{item.show ? (
<div className="wish">
<div className="wishatt">
{
item.isReadOnly ? (
<div>
<span className="wishatt capital">
Category:
{item.category}
</span>
{ShowEdit(item)}
<div className="wishatt">
Item Name:
{item.name}
</div>
<div className="wishatt">
Description:
{item.description}
</div>
<div className="wishatt">
Cost:
{item.cost}
</div>
<span>Link: </span>
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
<a className="wishatt" href="#" onClick={(e) => goToLink(item.link)}>{item.link}</a>
<div className="wishatt">
Quantity:
{item.quantity}
</div>
</div>
)
: (
<span>
<label htmlFor="category">
Category:
<select name="category" onChange={(e) => handleCategoryChange(e, item)} value={item.category}>
<option value="default">Default</option>
<option value="camping">Camping</option>
<option value="hendrix">Hendrix</option>
<option value="decor">Decor</option>
</select>
</label>
<span className="righthandSection">
{Submit(item)}
{Cancel(item)}
</span>
<div>
<div>
<label htmlFor="name">
Item Name:
</label>
<input className="wishatt" name="name" placeholder="Name" onChange={(e) => handleChange(e, item)} value={item.name} />
</div>
<div>
<label htmlFor="description">
Description:
</label>
<input className="wishatt" name="description" placeholder="Description" onChange={(e) => handleChange(e, item)} value={item.description} />
</div>
<div>
<label htmlFor="cost">
Cost:
</label>
<input className="wishatt" name="cost" placeholder="Cost" onChange={(e) => handleChange(e, item)} value={item.cost} />
</div>
<div>
<label htmlFor="link">
Link:
</label>
<input className="wishatt" name="link" placeholder="Link" onChange={(e) => handleChange(e, item)} value={item.link} />
</div>
<div>
<label htmlFor="quantity">
Quantity:
</label>
<input className="wishatt" name="quantity" placeholder="Quantity" onChange={(e) => handleChange(e, item)} value={item.quantity} />
</div>
<div className="wishatt">
Wishlist:
{item.wishlist}
</div>
</div>
</span>
)
}
</div>
</div>
) : null}
</div>
)
}
export default Wishlist;
I created a Django + React Todo App which is working as expected on the frontend but the functionality of updating the backend when a new item/todo is not know to me well. Also, i was able to manage the delete functionality but not the rest (got stuck with handleItem function). Need help to make the add items functionality work :(
Frontend Code below, Backend code is here "https://bitbucket.org/Yash-Marmat/backend/src/master/"
import React, { Component } from "react";
import axios from "axios";
import "bootstrap/dist/css/bootstrap.min.css";
import "font-awesome/css/font-awesome.min.css";
//import "#fortawesome/react-fontawesome";
import "./App.css";
class App extends Component {
constructor() {
super();
this.state = {
newID: 11,
edit: false,
cloneID: 0,
cloneTitle: "",
todoData: [], // will come from django backend
};
this.handleUserInput = this.handleUserInput.bind(this);
this.handleAddItem = this.handleAddItem.bind(this);
this.handleEdits = this.handleEdits.bind(this);
this.renderEdits = this.renderEdits.bind(this);
this.handleUpdates = this.handleUpdates.bind(this);
this.onRemove = this.onRemove.bind(this);
this.handleStrike = this.handleStrike.bind(this);
this.refreshList = this.refreshList.bind(this);
this.getTodos = this.getTodos.bind(this);
this.increment = this.increment.bind(this);
}
// increment id
increment() {
this.setState((prevState) => ({
newID: prevState.newID + 1,
}));
}
// Todo Creation Function (part 1)
handleUserInput(event) {
this.setState({
userInput: event.target.value,
});
}
// PROBLEM BELOW
// Todo Creation Function (part 2)
handleAddItem(id) {
const someID = this.state.newID;
//console.log(someID)
this.setState((prevState) => ({
todoData: [
...prevState.todoData,
{ id: someID, task: this.state.userInput },
],
userInput: "", // im telling react to empty my userInput (the input box)
}));
}
// Todo edit funciton (part 1)
// function 1 (runs the very first time (if edit gets clicked))
handleEdits(theId, theTitle) {
this.setState({
edit: true,
cloneID: theId,
cloneTitle: theTitle,
});
}
// Todo edit function (part 2)
// function 2 (runs automatically after function 1)
// (will run only when the edit condition is true (when we click on edit button))
renderEdits() {
if (this.state.edit) {
return (
<div>
<form onSubmit={this.handleUpdates}>
<input
autoFocus={true}
placeholder="Update Todos"
type="text"
name="data"
defaultValue={this.state.cloneTitle} // from the cloneTitle
className="form-control"
/>
<button
type="submit"
className="btn btn-sm btn-success ml-2 updateButton"
>
Update
</button>
</form>
</div>
);
}
}
// Todo edit Function (part 3)
// function 3 (will run when function 2 runs)
handleUpdates(event) {
event.preventDefault();
this.setState({
todoData: this.state.todoData.map((item) => {
if (item.id === this.state.cloneID) {
item["task"] = event.target.data.value;
return item;
} else {
return item;
}
}),
});
this.setState({
edit: false,
});
}
// Todo delete function
// onRemove(myId) {
// this.setState((prevState) => {
// const updatedList = prevState.todoData.filter((each) => each.id !== myId);
// return {
// todoData: updatedList
// };
// });
// }
onRemove(myId) {
axios
.delete(`http://localhost:8000/api/todos/${myId}`)
.then((res) => this.refreshList());
}
refreshList = () => {
axios
.get("http://localhost:8000/api/todos/")
.then((res) => this.setState({ todoData: res.data }))
.catch((err) => console.log(err));
};
handleStrike(theId, theTask) {
const todoData = this.state.todoData.map((item) => {
if (item.id === theId) {
item["id"] = theId;
item["task"] = theTask;
item["completed"] = !item.completed;
return item;
} else {
return item;
}
});
this.setState({
todoData: todoData,
});
}
// Lifecycle Method
componentDidMount() {
this.getTodos(); // managing the django apis
}
// working with componentDidMount Method
getTodos() {
axios
.get("http://127.0.0.1:8000/api/todos")
.then((res) => {
this.setState({ todoData: res.data });
})
.catch((err) => {
console.log(err);
});
}
render() {
console.log(this.state.todoData)
return (
<div className="card mb-3 sizing mx-auto">
{this.renderEdits()}
{this.state.todoData.map((item) => (
<div className="card-body border text-grey" key={item.id}>
<span className={"crossed-line" + (item.completed ? "" : "active")}>
{item.task}
</span>
{/* edit button below */}
<span
onClick={() => this.handleEdits(item.id, item.task)}
className="shift2 mr-1"
>
<i className="fas fa-edit"></i>
</span>
{/* delete button */}
<span onClick={() => this.onRemove(item.id)} className="mr-2">
<i className="shift fas fa-trash ml-20"></i>
</span>
<span
className="shift3"
onClick={() => this.handleStrike(item.id, item.task)}
>
{item.completed ? (
<i className="fas fa-undo-alt"></i>
) : (
<i className="fas fa-check-circle"></i>
)}
</span>
</div>
))}
<br />
<span>
<input
autoFocus={true}
placeholder="Add Todos"
value={this.state.userInput || ""}
onChange={this.handleUserInput}
className="form-control"
/>
<span
style={{ color: "purple" }}
className="addButton"
onClick={() => {
this.handleAddItem();
this.increment();
}}
disabled={!this.state.userInput}
>
<i className="fas fa-plus-square shift ml-20"></i>
</span>
</span>
</div>
);
}
}
export default App;
I am using MERN stack and Redux. I have created a function to update a property within a database. I have tested the api on Postman and it works. When i try and run it it seems to clash with another function and i get the error 'TypeError: this.props.subjects.map is not a function' which works prior to the trueVote function being called. Anyone any idea what i am missing here?
Print outs show the action and reducer are being hit but not the api, even though that works on Postman. The function is being called from within the voteHandler
EDIT: The console.log message within the api doesn't print but shows in the terminal window on VS when i refresh my page after the error the function has done what it should and the relevant data has been updated. Is it a case of managing the error? If so how would i do this so it doesn't crash the app?
api
// put req for a true vote
subjectRouter.put("subject/true/:_id/:currTrue", (req, res) => {
console.log("True api hitting");
Subject.findOneAndUpdate(
{ _id: req.params._id },
{
true: Number(req.params.currTrue) + 1,
},
{
new: true,
useFindAndModify: false,
}
)
.then((subject) => res.json(subject))
.catch((err) => console.log(err));
});
action
// true vote
export const trueVote = (_id, currTrue) => (dispatch) => {
console.log("trueVote hitting");
fetch(`/api/subjects/subject/true/${_id}/${currTrue}`, {
method: "PUT",
})
.then((res) => res.json())
.then((subject) =>
dispatch({
type: TRUE_VOTE,
subjects: subject,
})
);
};
reducer
case TRUE_VOTE:
console.log("true reducer hitting");
return {
...state,
items: action.subjects,
};
component
import React, { Component } from "react";
import PropTypes from "prop-types";
import GoogleSearch from "./GoogleSearch";
import { connect } from "react-redux";
import { fetchLatestSubjects } from "../../actions/subject";
import { fetchTopicSubjects } from "../../actions/subject";
import { fetchTopicComments } from "../../actions/comment";
import { fetchComments } from "../../actions/comment";
import { rateSubject } from "../../actions/subject";
import { fetchUsers } from "../../actions/authActions";
import { rateUser } from "../../actions/authActions";
import { rateComment } from "../../actions/comment";
import { trueVote } from "../../actions/subject";
import { falseVote } from "../../actions/subject";
class Subject extends Component {
// on loading the subjects and comments
// are fetched from the database
componentDidMount() {
this.props.fetchLatestSubjects();
this.props.fetchComments();
this.props.fetchUsers();
}
constructor(props) {
super(props);
this.state = {
// set inital state for subjects
// description, summary and comments all invisible
viewDesription: -1,
viewSummary: -1,
comments: [],
topic: "subjects",
};
}
componentWillReceiveProps(nextProps) {
// new subject and comments are added to the top
// of the arrays
if (nextProps.newPost) {
this.props.subjects.unshift(nextProps.newPost);
}
if (nextProps.newPost) {
this.props.comments.unshift(nextProps.newPost);
}
}
clickHandler = (id) => {
// when a subject title is clicked pass in its id
const { viewDescription } = this.state;
this.setState({ comments: [] });
var temp = [];
// get the details of the author of the subject and save to state
const subject = this.props.subjects.find((subject) => subject._id === id);
const user = this.props.users.find((user) => user._id === subject.author);
// save comments for subject to temp array
var i;
for (i = 0; i < this.props.comments.length; i++) {
if (this.props.comments[i].subject === id) {
temp.unshift(this.props.comments[i]);
}
}
console.log(temp);
// for each comment add a property with the authors name
temp.forEach((comment) => {
var commentAuthor = this.props.users.find(
(user) => user._id === comment.author
);
comment.authName = commentAuthor.name;
});
// save the subject id to local storage
// this is done incase a new comment is added
// then the subject associated with it can be retrieved
// and added as a property of that comment
localStorage.setItem("passedSubject", id);
localStorage.setItem("passedTopic", subject.topic);
// add all changes to the state
this.setState({
viewDescription: viewDescription === id ? -1 : id,
comments: temp,
subAuthor: user.name,
authRating: user.rating,
authNoOfVotes: user.noOfVotes,
});
};
// hovering on and off subjects toggles the visibility of the summary
hoverHandler = (id) => {
this.setState({ viewSummary: id });
};
hoverOffHandler = () => {
this.setState({ viewSummary: -1 });
};
rateHandler = (id, rate, item) => {
if (item === "subject") {
// this function rates the subject and the author
const subject = this.props.subjects.find((subject) => subject._id === id);
const author = this.props.users.find(
(user) => user._id === subject.author
);
// call the rateSubject and rateUser functions
this.props.rateSubject(id, rate, subject.noOfVotes, subject.rating);
this.props.rateUser(author._id, rate, author.noOfVotes, author.rating);
console.log(author.name);
alert("Thank you for rating this subject.");
} else if (item === "comment") {
const comment = this.props.comments.find((comment) => comment._id === id);
const author = this.props.users.find(
(user) => user._id === comment.author
);
// call the rateComment and rateUser functions
this.props.rateComment(id, rate, comment.noOfVotes, comment.rating);
this.props.rateUser(author._id, rate, author.noOfVotes, author.rating);
console.log(author.name);
alert("Thank you for rating this comment.");
}
};
voteHandler = (id, currVote, vote) => {
if (vote == "True") {
console.log(id, currVote, vote);
this.props.trueVote(id, currVote);
} else if (vote == "False") {
console.log(id, currVote, vote);
this.props.falseVote(id, currVote);
}
};
render() {
const subjectItems = this.props.subjects.map((subject) => {
// if the state equals the id set to visible if not set to invisible
var view = this.state.viewDescription === subject._id ? "" : "none";
var hover = this.state.viewSummary === subject._id ? "" : "none";
var comments = this.state.comments;
var subjectAuthor = this.state.subAuthor;
var authRating = this.state.authRating;
var authNoOfVotes = this.state.authNoOfVotes;
var className = "";
if (subject.category === "Education") {
className = "Education";
} else if (subject.category === "Environment") {
className = "Environment";
} else if (subject.category === "Politics") {
className = "Politics";
} else if (subject.category === "Health") {
className = "Health";
} else if (subject.category === "Other") {
className = "Other";
}
return (
<div key={subject._id}>
<div
className={className}
onMouseEnter={() => this.hoverHandler(subject._id)}
onMouseLeave={() => this.hoverOffHandler()}
>
<p className="title" onClick={() => this.clickHandler(subject._id)}>
{subject.title}
</p>
<p className="vote" style={{ textAlign: "Right" }}>
True:{" "}
{((100 / (subject.true + subject.false)) * subject.true).toFixed(
1
)}
% {" False: "}
{((100 / (subject.true + subject.false)) * subject.false).toFixed(
1
)}
%
</p>
<p className="summary" style={{ display: hover }}>
{subject.summary}
</p>
</div>
<div className="subjectBody " style={{ display: view }}>
<div className="leftSubjectBody">
<div className="subjectAuthor">
<p className="author">
Subject created by: {subjectAuthor} -{" "}
{(authRating / authNoOfVotes).toFixed(1)}/5 Star user
{/* <br /> {subject.date} */}
</p>
</div>
<div className="subjectDescription">
<p className="description">{subject.description}</p>
</div>
<div className="subjectLinks">Links: {subject.links}</div>
</div>
<div className="rightSubjectBody">
<div className="rate">
<p> Rate this subject:</p>
<br />
<button
onClick={() => this.rateHandler(subject._id, 1, "subject")}
>
1
</button>
<button
onClick={() => this.rateHandler(subject._id, 2, "subject")}
>
2
</button>
<button
onClick={() => this.rateHandler(subject._id, 3, "subject")}
>
3
</button>
<button
onClick={() => this.rateHandler(subject._id, 4, "subject")}
>
4
</button>
<button
onClick={() => this.rateHandler(subject._id, 5, "subject")}
>
5
</button>
<p>
Rating: {(subject.rating / subject.noOfVotes).toFixed(1)}/5
</p>
</div>
<div className="voting">
<p>
Do you think this subject question is true or false based on
the evidence provided and your own reseach in the area? <br />
</p>
<p>Please vote and leave comments.</p>
<br />
<div
className="voteButton"
onClick={() =>
this.voteHandler(subject._id, subject.true, "True")
}
>
TRUE
</div>
<div
className="voteButton"
onClick={() =>
this.voteHandler(subject._id, subject.false, "False")
}
>
FALSE
</div>
</div>
</div>
<div className="subjectComments">
<p style={{ fontWeight: "bold" }}>Comments:</p>
{comments.map((comment, i) => {
return (
<div key={i} className="singleComment">
<p>
{comment.title}
<br />
{comment.comment}
<br />
Comment by : {comment.authName} - This user has a rating
of {(comment.rating / comment.noOfVotes).toFixed(1)}/5
STARS
</p>
<div className="rate">
Rate this comment:
<button
onClick={() =>
this.rateHandler(comment._id, 1, "comment")
}
>
1
</button>
<button
onClick={() =>
this.rateHandler(comment._id, 2, "comment")
}
>
2
</button>
<button
onClick={() =>
this.rateHandler(comment._id, 3, "comment")
}
>
3
</button>
<button
onClick={() =>
this.rateHandler(comment._id, 4, "comment")
}
>
4
</button>
<button
onClick={() =>
this.rateHandler(comment._id, 5, "comment")
}
>
5
</button>
<p>
Rating:{" "}
{(comment.rating / comment.noOfVotes).toFixed(1)}/5
</p>
</div>
</div>
);
})}
<br />
<a href="/addcomment">
<div className="buttonAddComment">ADD COMMENT</div>
</a>
</div>
</div>
</div>
);
});
return (
<div id="Subject">
<GoogleSearch />
{subjectItems}
</div>
);
}
}
Subject.propTypes = {
fetchLatestSubjects: PropTypes.func.isRequired,
fetchTopicSubjects: PropTypes.func.isRequired,
fetchTopicComments: PropTypes.func.isRequired,
fetchComments: PropTypes.func.isRequired,
fetchUsers: PropTypes.func.isRequired,
rateSubject: PropTypes.func.isRequired,
rateComment: PropTypes.func.isRequired,
rateUser: PropTypes.func.isRequired,
trueVote: PropTypes.func.isRequired,
falseVote: PropTypes.func.isRequired,
subjects: PropTypes.array.isRequired,
comments: PropTypes.array.isRequired,
users: PropTypes.array.isRequired,
newPost: PropTypes.object,
};
const mapStateToProps = (state) => ({
subjects: state.subjects.items,
newSubject: state.subjects.item,
comments: state.comments.items,
users: state.auth.users,
newComment: state.comments.item,
});
// export default Subject;
export default connect(mapStateToProps, {
fetchLatestSubjects,
fetchTopicSubjects,
fetchTopicComments,
fetchComments,
fetchUsers,
rateSubject, // rate subject
rateUser,
rateComment,
trueVote,
falseVote,
})(Subject, Comment);
Because you are returning a single subject as part of your reducer action, you presumably want to exchange the existing subject with the updated subject, so you would need to update your reducer to be:
case TRUE_VOTE:
const index = state.items.subjects.findIndex( subject => action.subjects.id === subject.id );
return {
items: [...state.items.slice(0, index), action.subjects, ...state.items.slice( index + 1 )] };
};
To make it a bit more clear, you could of course also change your action to indicate it's only a single subject you are returning
// true vote
export const trueVote = (_id, currTrue) => (dispatch) => {
console.log("trueVote hitting");
fetch(`/api/subjects/subject/true/${_id}/${currTrue}`, {
method: "PUT",
})
.then((res) => res.json())
.then((subject) =>
dispatch({
type: TRUE_VOTE,
subject
})
);
};
Which would then be more clear inside your reducer that you are only expecting 1 subject
My code works but I feel like there's a way to do this without declaring a ton of state.
When the nav is clicked, it opens all SectionHeaders, and when one of those SectionHeaders is clicked, it opens the SubSections (only one SubSection allowed to be opened at once)
isFilterOpen
Open but subs closed
One sub open (only one at a time, they toggle)
Right now, my code looks like this:
class MobileFilter extends React.Component {
constructor(props) {
super(props);
this.state = {
isFilterOpen: false,
isSectionOpen: {
Business: false,
Resource: false,
Need: false,
Implementation: false,
Type: false,
Foundations: false,
Advantage: false,
Advanced: false,
Catalyst: false,
Team: false,
},
};
this.filterBar = React.createRef();
}
handleFilterClick = () => {
const {
isFilterOpen
} = this.state;
this.setState({
isFilterOpen: !isFilterOpen,
});
};
handleSectionClick = title => {
let selectedSection = title;
if (title.split(' ').length > 1) {
selectedSection = title.split(' ')[0]; // eslint-disable-line
}
this.setState(prevState => {
const newState = {};
Object.keys(prevState.isSectionOpen).forEach(key => {
newState[key] = false;
});
newState[selectedSection] = !prevState.isSectionOpen[selectedSection];
return {
...prevState,
isSectionOpen: {
...newState,
},
};
});
};
render() {
const { isFilterOpen } = this.state;
const {
need = '',
implementation = '',
type = '',
customerStoriesURL = '',
vertical,
} = this.props;
const filterClasses = isFilterOpen
? 'showMobileSections'
: 'hideMobileSections';
const wrapperClass = isFilterOpen
? 'mobileFilterWrapperActive'
: 'mobileFilterWrapper';
const filterData = this.getData(vertical);
if (vertical === 'services') {
return (
<div className="filterBarMobile" ref={this.filterBar}>
<div className="mobileFilterWrapperContainer">
<div className={wrapperClass}>
<button
type="button"
onClick={this.handleFilterClick}
className="filterHead"
>
Navigate Hub
</button>
<div className={filterClasses}>
{this.renderSections('Foundations', filterData.Foundations)}
</div>
<div className={filterClasses}>
{this.renderSections('Advantage', filterData.Advantage)}
</div>
<div className={filterClasses}>
{this.renderSections('Advanced', filterData.Advanced)}
</div>
<div className={filterClasses}>
{this.renderSections('Catalyst', filterData.Catalyst)}
</div>
<div className={filterClasses}>
{this.renderSections(
'Team Edition',
filterData['Team Edition'],
)}
</div>
</div>
</div>
</div>
);
}
return (
<div className="filterBarMobile" ref={this.filterBar}>
<div className="mobileFilterWrapperContainer">
<div className={wrapperClass}>
<button
type="button"
onClick={this.handleFilterClick}
className="filterHead"
>
Navigate Hub
</button>
<div className={filterClasses}>
{this.renderSections(need, filterData.need)}
</div>
{implementation ? (
<div className={filterClasses}>
{this.renderSections(implementation, filterData.implementation)}
</div>
) : null}
<div className={filterClasses}>
{this.renderSections(type, filterData.type)}
</div>
<div className={filterClasses}>
<div className="sectionTab">
<Link className="sectionLabel" to={customerStoriesURL}>
Customer Stories
</Link>
</div>
</div>
</div>
</div>
</div>
);
}
}
export default MobileFilter;
As you can see, there's way too much state going on -- there as to be a way to make this more founded on the data / props that are coming in and not in a way that requires me listing out all of the SubSections as a nested state.
Any ideas would help. Thanks!
i think i've found the solution. i needed to start from scratch. here's what i have:
import React, { Component } from 'react';
import { Link } from 'gatsby';
import Search from '../Search';
import { businessData } from './filterData';
import './newFilter.less';
class NewFilter extends Component {
constructor(props) {
super(props);
this.state = {
isOpen: false,
openSubSection: '',
};
}
handleClick = () => {
const { isOpen } = this.state;
if (!isOpen) {
this.setState({
openSubSection: '',
});
}
this.setState({
isOpen: !isOpen,
});
};
handleSubClick = (e, title) => {
const { openSubSection } = this.state;
if (openSubSection === title) {
this.setState({
openSubSection: '',
});
} else {
this.setState({
openSubSection: title,
});
}
};
// renderLinks = sublevels => sublevels.map(({ title }) => <div>{title}</div>);
renderLinks = sublevels =>
sublevels.map(({ url_slug, title }) => {
if (!url_slug) {
return (
<div className="sectionLabelSub" key={title}>
{title}
</div>
);
}
return (
<Link
className="mobileSubLinks"
key={url_slug}
to={`/${url_slug}/`}
style={{ display: 'block' }}
>
{title}
</Link>
);
});
renderSection = section => {
const { isOpen, openSubSection } = this.state;
const { title, sublevels } = section;
let sectionClass = 'hideMobileSections';
let sectionOpen = 'sectionTabClosed';
let subSectionClass = 'hideMobileContent';
let arrowClass = 'arrow arrow--active';
if (isOpen) {
sectionClass = 'showMobileSections';
}
if (openSubSection === title) {
subSectionClass = 'showMobileContent';
sectionOpen = 'sectionTabOpen';
arrowClass = 'arrow';
}
// const sectionClass = isOpen ? 'section__open' : 'section__closed';
return (
<div className={sectionClass}>
<button
onClick={e => this.handleSubClick(e, title)}
type="button"
key={title}
className={sectionOpen}
>
<button type="button" className="sectionLabel">
{title}
</button>
<div className={arrowClass} />
</button>
<div className={subSectionClass} role="button" tabIndex="0">
{this.renderLinks(sublevels)}
</div>
</div>
);
};
renderSections = sections =>
sections.map(section => this.renderSection(section));
render() {
const { isOpen } = this.state;
const { navTitle, sections } = businessData;
let wrapperClass = 'mobileFilterWrapper';
if (isOpen) {
wrapperClass = 'mobileFilterWrapperActive';
}
return (
<div className="filterBarMobile" ref={this.filterBar}>
<Search vertical='business' />
<div className="mobileFilterWrapperContainer">
<div className={wrapperClass}>
<button
onClick={() => this.handleClick()}
type="button"
className="filterHead"
>
{navTitle}
</button>
{this.renderSections(sections)}
</div>
</div>
</div>
);
}
}
export default NewFilter;
basically i let the data inform the components, pass in the title to the button and the click event, and then the class looks to see if the title from the data matches the title (string) attached to the state
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>