I am pretty new to react and got really stuck on something. I am working on a sort of ordering application. People can order a product and can select all ingredients they want. I was thinking to do this with a checkbox for each ingredient. Unfort. I just don't know how to get this fixed. Also, I am wondering if I have to use a state in my component or just a variable.
So I am mapping through the array of ingredients and for each ingredient I am displaying a checkbox to turn on/off an ingredient. So my main question, How can I adjust my object with these checkboxes, and if I need to have a state in my component to keep up to date with the checkboxes, How will I set the product to my state? Because It's coming from props.
I've tried all sort of things, for instance this from the docs:
constructor(props) {
super(props);
this.state = {
isGoing: true,
numberOfGuests: 2
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
But then again, How can I add the product to my state? Also, this will be different since the ingredients are in a object, and the example from the docs are just values and not in a specific object.
My component
import React from 'react';
import { Link } from 'react-router-dom';
class Order extends React.Component {
constructor(props) {
super(props);
}
handleToggle(e) {
//Handle the toggle, set the value of the ingredient to 0 or 1
}
getData(e, product) {
e.preventDefault();
console.log(product)
}
render() {
const product = this.props.products.find((product) => {
return product.id == this.props.match.params.id;
});
return (
<form className="container mx-auto px-4 pt-6" onSubmit={(e) => this.getData(e, product) }>
<Link to={`/${this.props.match.params.category}`} className="mb-4 relative block text-brand hover:text-brand-dark">← Terug naar categorie</Link>
<div className="flex flex-row items-center justify-between bg-white rounded px-8 py-8 shadow-sm mb-4">
<div className="">
<h2 className="text-brand uppercase">{product && product.name}</h2>
<div className="ingre">
<p>
{product && product.ingredients.map((item) => {
return <span className="ing text-grey-dark text-sm" key={item.name}>{item.name}</span>
})}
</p>
</div>
</div>
<div className="">
<h3 className="text-brand text-4xl">€{product && product.price}</h3>
</div>
</div>
<div className="flex flex-wrap mb-4 -mx-2">
{product && product.ingredients.map((item) => {
return (
<div className="w-1/2 mb-4 px-2" key={item.name}>
<div className="flex flex-row items-center justify-between bg-white rounded px-8 py-8 shadow-sm">
<div>
<h3 className="text-grey-dark font-normal text-sm">{item.name}</h3>
</div>
<div>
<input type="checkbox" checked={item.value} name={item} onChange={(e) => this.handleToggle(e)}/>
</div>
</div>
</div>
);
})}
</div>
<button type="submit" className="bg-brand hover:bg-brand-dark text-white font-bold py-4 px-4 rounded">
Order this product
</button>
</form>
);
}
}
export default Order;
An example of a product
So actually I need to keep track of the product and bind the value's of the ingredients to the checkbox. If it's not checked the value must become 0 (or false).
Edit:
Parent component passing props
// React deps
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
// Custom components
import Navigation from './components/General/Navigation'
// Pages
import Index from './components/Index'
import Category from './components/Category'
import Order from './components/Order'
// Data
import products from './Data'
class App extends Component {
constructor(props) {
super(props);
this.state = {
products: []
}
}
componentWillMount() {
setTimeout(() => {
this.setState({products});
}, 100);
}
render() {
return (
<main className="App font-sans">
<Router>
<div>
<Navigation logo="Jackies" />
<Switch>
<Route exact path="/" component={Index} />
<Route exact path="/:category" render={(props) => <Category {...props} products={this.state.products} />}/>
<Route exact path="/:category/:id" render={(props) => <Order {...props} products={this.state.products} />}/>
</Switch>
</div>
</Router>
</main>
);
}
}
export default App;
In the parent, you will pass a handler function in a onIngredientToggle prop:
<Route exact path="/:category/:id" render={(props) => <Order {...props} products={this.state.products} onIngredientToggle={this.handleIngredientToggle} />}/>
Then define the handleIngredientToggle function:
function handleIngredientToggle(productId, ingredientIndex, newIngredientValue) {
// basically this goes shallow cloning the objects and arrays up to
// the point it changes the ingredient value property
let products = [...this.state.products];
let modifiedProductIndex = products.findIndex(p => p.id === productId);
let product = {...products[modifiedProductIndex]};
products[modifiedProductIndex] = product;
product.ingredients = [...products[modifiedProductIndex].ingredients];
product.ingredients[ingredientIndex] = {...product.ingredients[ingredientIndex], value: newIngredientValue};
this.setState({products});
}
// If you prefer, the above function can be replaced with:
function handleIngredientToggle(productId, ingredientIndex, newIngredientValue) {
// deep clone products
let products = JSON.parse(JSON.stringify(this.state.products));
// modify just what changed
products.find(p => p.id === productId).ingredients[ingredientIndex].value = newIngredientValue;
this.setState({products});
}
In the Order child, you will add the index argument to the map (you have two of these, just add to the second):
{product && product.ingredients.map((item, index) => {
In the checkbox pass the product and index to the handleToggle function as argument:
<input type="checkbox" checked={item.value} name={item} onChange={(e) => this.handleToggle(e, product, index)}/>
And then in the function implementation, you will call the function received as prop from the parent:
handleToggle(e, product, index) {
this.props.onIngredientToggle(product.id, index, e.target.checked);
}
Thanks to #acdcjunior who opened my (tired) eyes, I've found a solution
Checkbox html
<input type="checkbox" checked={item.value} name={item} onChange={(e) => this.handleToggle(e, item, product)}/>
Function on the child component
handleToggle(e, item, product) {
//Get value from checkbox and set it the opposite
let value = e.target.checked ? 1 : 0;
//Pass down the item (ingredient) and parent product and also the value
this.props.toggleIngredient(item, product, value);
}
Function on parent component to change the state
toggleIngredient(i, p, v) {
// Get the product from the state
var product = this.state.products.find((product) => {
return product.id == p.id
});
// Filter down the state array to remove the product and get new products array
let products = this.state.products.filter((product) => {
return product != product;
});
// Get the ingredient object
var object = product.ingredients.find((product) => {
return product == i;
});
// Set the value for the ingredient, either true or false (depends on checkbox state)
object.value = v;
// Push the edited product the array of products
products.push(product);
// Set the state with the new products array
this.setState({
products: products
});
}
Related
I'm working on a project ( Airbnb clone - for personal learning), and on this project I am trying to understand concepts and conventions thoroughly.
Here's the problem:
On this page I use a date input custom component like this:
-- parent component -
import { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { testData } from "../utils/mockData";
import Header from "../components/header/Header";
import DateSearchBar from "../components/header/DateSearchBar";
import Guests from "../components/cards/Guests";
const Property = () => {
const propertyId = useParams();
const convertedId = parseInt(propertyId.id);
const [noOfGuests, setNoOfGuests] = useState(0);
const [fromDate, setFromDate] = useState();
const [toDate, setToDate] = useState();
const selectedProperty = testData.filter(item => item.id === propertyId.id)
const handleGuests = (val) => {
if (noOfGuests === 0) {
setNoOfGuests(0);
}
setNoOfGuests(noOfGuests + val)
}
const handleDate = (val) => {
setFromDate(val);
}
return (
<div>
<Header />
<div className="flex justify-center space-x-24 mt-6">
<div className=" max-w-sm rounded-xl overflow-hidden shadow-sm w-9/12">
<img
className=" text-centerw w-96 rounded-md h-64"
src={selectedProperty[0].image}
/>
<p className="h-16">{selectedProperty[0].title}</p>
</div>
<div className="ml-96">
<h4 className="text-center italic font-extrabold">From</h4>
<DateSearchBar name="fromDate" value={fromDate} handleDate={handleDate } />
<h4 className="text-center italic font-extrabold">To</h4>
<DateSearchBar name="toDate" value={fromDate} handleDate={handleDate } />
<Guests handleGuests={handleGuests} noOfGuests={noOfGuests} />
</div>
</div>
</div>
);
};
export default Property;
--- Child Component ---
import React from "react";
const DateSearchBar = ({ handleDate }) => {
return (
<div>
{/* fromDate */}
<div className="text-center">
<input
className="bg-slate-50 hover:bg-red-200 rounded-md h-12 w-80 text-center mb-16 "
type="date"
onChange={(e) => handleDate(e.target.value)}
/>
</div>
</div>
);
};
export default DateSearchBar;
The Property.js component owns the local state, I am using a callback function to set a local state in the parent component.
The problem is that I need to differ between the fromDate state and the toDate state in the parent component, but I'm not sure how to write this logic.
Obviously I can set up another date component and target it, however it beats the purpose of creating and using reusable components and keeping your code DRY.
Also, Redux/Context seem too much for this issue ( but I might be wrong)
Any ideas on how I can solve this ?
First thing is, that you are passing fromDate in both of the components. I believe you should have a value of fromDate in the first and toDate in the second.
Next, to be able to reuse handleDate for both inputs, you need to pass the element's name back to the parent along with the value and then use that name argument to differentiate between the two components.
In parent:
const handleDate = (name, value) => {
if (name === "fromDate") {
setFromDate(value)
} else if (name === "toDate") {
setToDate(value)
}
}
In child:
onChange={e => handleDate(props.name, e.target.value)}
Another approach would be to return a method from handleDate():
In parent:
const handleDate = (name) => {
if (name === "fromDate") {
return (value) => setFromDate(value)
} else if (name === "toDate") {
return (value) => setToDate(value)
}
}
...
<DateSearchBar name="fromDate" value={fromDate} handleDate={handleDate("fromDate")} />
<DateSearchBar name="toDate" value={toDate} handleDate={handleDate("toDate")} />
In this case, you don't have to change child component.
However IMO this still isn't the simplest approach. Yes, we should try to follow these clean code recommendations but only to the point where they don't lead to further complexity. For example, in the above case we are over-complicating handleDate(), it would be a lot simpler to have separate inline change handlers for each component:
<DateSearchBar name="fromDate" value={fromDate} handleDate={val => setFromDate(val)} />
<DateSearchBar name="toDate" value={toDate} handleDate={val => setToDate(val)} />
If our form grows bigger, we can use dedicated form handling React libraries such as Formik and React-hook-form to keep our component logic simpler.
I have a basic app which simply returns a number of cards with some content inside, I have some buttons which then filter the returned cards by a value found in the dataset. The filter buttons do work indididually but if I click one after the other the filter is being applied to the now filtered data. How can I make sure the filter is being applied to the initial state of the data or how can I reset the state to everything before the filter is applied? Thanks.
parent.js
import './App.scss';
import DataThoughts from "./assets/data/DataThoughts";
import Thoughts from "./components/Thoughts";
function AppThoughts() {
return (
<div className="App">
<main className={'bg-light'}>
<div className={'container'}>
<Thoughts data={DataThoughts} />
</div>
</main>
</div>
);
}
export default AppThoughts;
Thoughts.js
import React, { Component } from 'react';
import FilterButton from "./FilterButton";
class Thoughts extends Component {
constructor(props) {
super(props);
this.state = {...props};
}
handleClick = value => () => {
let filtered = this.state.data.filter(item => item.Emotion === value);
this.setState({ data: filtered });
console.log(this.state.data);
};
render() {
let data = this.state.data;
let numberOfThoughts = data.length;
let dataList = this.state.data.map((thought, i) =>
<div className={`col-12 col-sm-12 col-md-6 ${i % 2 === 0 ? i % 3 === 0 ? 'col-lg-3 col-xl-3' : 'col-lg-5 col-xl-5' : 'col-lg-4 col-xl-4'}`} key={'thought'+i}>
<div className="card mb-4">
{thought.Photo ? <img src={thought.Photo} className="card-img-top" alt={thought.Emotion}/> : ''}
<div className="p-5">
<blockquote className="blockquote mb-0">
<p className={'small text-muted'}>{thought.Date}</p>
<p className={`${i % 2 === 0 ? i % 3 === 0 ? 'display-6' : 'display-4' : 'display-5'} mb-4`}>{thought.Thought}</p>
<footer className="small text-muted">{thought.Author}, <cite title="Source Title">{thought.Location}</cite></footer>
</blockquote>
</div>
</div>
</div>
);
return (
<section className="row section-row justify-content-start thoughts">
<div className={`col-12`} key={'filters'}>
<FilterButton buttonText={"Happy"} onClick={this.handleClick('Happy')} />
<FilterButton buttonText={"Sad"} onClick={this.handleClick('Sad')} />
<FilterButton buttonText={"Thinking"} onClick={this.handleClick('Thinking')} />
<FilterButton buttonText={"All"} onClick={this.handleClick('All')} />
</div>
{dataList}
<div className={`col-12 col-sm-12 col-md-6 col-lg-2 col-xl-2 d-flex align-items-center justify-content-center`} key={'total'}>
<span className={'display-1'}>{numberOfThoughts}</span>
</div>
</section>
);
}
}
Thoughts.defaultProps = {
Photo: '',
Emotion:'Happy',
Date:'Morning',
Thought:'Default',
Author:'Me',
Location:'Somewhere'
};
export default Thoughts; // Don’t forget to use export default!
FilterButtons.js
import React, { Component } from 'react';
class FilterButton extends Component {
render() {
return (
<button className={'btn btn-primary d-inline-block mb-4'} onClick={this.props.onClick}>{this.props.buttonText}</button>
);
}
}
export default FilterButton; // Don’t forget to use export default!
It looks like the initial data comes from the props. You can access the props with this.props. So you can do something like this:
handleClick = value => () => {
// filter the initial data
let filtered = this.props.data.filter(item => item.Emotion === value);
this.setState({ data: filtered });
// set to initial data
// this.setState({ ...this.props });
console.log(this.state.data);
};
i have an array, called reportsData, then i need to filter it, generating some checkboxes with each of them having a label based on each name that comes from another array (emittersData), so basically i set it like this:
const [searchUser, setSearchUser] = useState<string[]>([])
const mappedAndFiltered = reportsData
.filter((value: any) =>
searchUser.length > 0 ? searchUser.includes(value.user.name) : true
)
Then i render my checkboxes like this:
function EmittersCheckboxes () {
const [checkedState, setCheckedState] = useState(
new Array(emittersData.length).fill(false)
)
const handleOnChange = (position: any, label: any) => {
const updatedCheckedState = checkedState.map((item, index) =>
index === position ? !item : item
)
setSearchUser((prev) =>
prev.some((item) => item === label)
? prev.filter((item) => item !== label)
: [...prev, label]
)
setCheckedState(updatedCheckedState)
};
return (
<div className="App">
{emittersData.map((value: any, index: any) => {
return (
<li key={index}>
<div className="toppings-list-item">
<div className="left-section">
<input
className="h-4 w-4 focus:bg-indigo border-2 border-gray-300 rounded"
type="checkbox"
id={`custom-checkbox-${index}`}
name={value.Attributes[2].Value}
value={value.Attributes[2].Value}
checked={checkedState[index]}
onChange={() => handleOnChange(index, value.Attributes[2].Value)}
/>
<label className="ml-3 font-medium text-sm text-gray-700 dark:text-primary" htmlFor={`custom-checkbox-${index}`}>{value.Attributes[2].Value}</label>
</div>
</div>
</li>
);
})}
</div>
)
}
And on the react component i am rendering each checkbox, that is a li, like:
<ul><EmittersCheckboxes /></ul>
And i render the mappedAndFiltered on the end.
Then it is fine, when i click each generated checkbox, it filters the array setting the state in setSearch user and the array is filtered.
You can check it here: streamable. com /v6bpk6
See that the filter is working, the total number of items in the array is changing based on the checkbox selected (one or more).
But the thing is that each checkbox does not become 'checked', it remains blank (untoggled).
What am i doing wrong, why doesnt it check itself?
You've defined your EmittersCheckboxes component inside another component. and every time that the parent component renders (by state change) your internal component is redefined, again and again causing it to lose it's internal state that React holds for you.
Here's a simplified example:
import React, { useState } from "react";
function CheckboxeComponent() {
const [checkedState, setCheckedState] = useState(false);
return (
<div>
<span>CheckboxeComponent</span>
<input
type="checkbox"
checked={checkedState}
onChange={() => setCheckedState((x) => !x)}
/>
</div>
);
}
export default function App() {
const [counter, setCounter] = useState(1);
function InternalCheckboxeComponent() {
const [checkedState, setCheckedState] = useState(false);
return (
<div>
<span>InternalCheckboxeComponent</span>
<input
type="checkbox"
checked={checkedState}
onChange={() => setCheckedState((x) => !x)}
/>
</div>
);
}
return (
<>
<InternalCheckboxeComponent />
<CheckboxeComponent />
<button onClick={() => setCounter((c) => c + 1)}>{counter}</button>
</>
);
}
There's the App (parent component) with its own state (counter), with a button to change this state, clicking this button will increase the counter, causing a re-render of App. This re-render redefines a new Component named InternalCheckboxeComponent every render.
The InternalCheckboxeComponent also has an internal state (checkedState).
And there's an externally defined functional component named CheckboxeComponent, with this component React is able to hold its own state, because it's not redefined (It's the same function)
If you set the state of each to be "checked" and click the button, this will cause a re-render of App, this will redefine the InternalCheckboxeComponent function, causing React to lose its state. and the CheckboxeComponent state remains in React as it's the same function.
Background
I'm building an e-commerce site with a fake products API, I have a component which renders a list of products, and a details displaying component. I would like to add some sort of prompt or ideally disable 'Add to cart' buttons in both list item component and details component while item is in the cart.
I'm using React and Context API to pass state (as a learning project), I've tried to add state each item list but then I have no way of passing it to details components, or to cart component so when user clears the cart, buttons go back to normal.
Potential solutions
The only solution I came up with is using indexOf to test if the item is in the cart once the 'Add to cart' button is clicked and then display an info the the user if it is (not sure how to do it though)
Is there any way to track the state of each induvidual item and then for example change the button to disabled?
Product component :
const Product = (props) => {
const CartValue = useCart();
const DetailValue = useDetail();
const ModalValue = useModal();
const {image_link, price, name} = props.product;
const product = props.product;
// updates cart and opens modal
const handleCart = () => {
if(CartValue.cart.indexOf(product)) {
CartValue.setCart(prev => [...prev, product]);
ModalValue.setModal({modalItem: product, modalOpen: true});
} else {
console.log('lol');
}
};
return (
<ProductWrapper className='col-9 mx-auto col-md-5 col-lg-3 my-3'>
<div className="card">
{/* TO DO Persist to local storage so user can bookmark link directly to Details component, or make fetch request? might take too long TO DO */}
<div className="img-container p-4" onClick={() => DetailValue.setDetail(props.product)}>
<Link to='/details'>
<img src={image_link} alt="product" className='card-img-top'/>
</Link>
{/* add to cart button */}
<button className="cart-btn" onClick={() =>handleCart()}>
<i className="fas fa-cart-plus"/>
</button>
</div>
{/* footer */}
<div className="card-footer d-flex justify-content-between">
<p className='align-self-center mb-0'>{name}</p>
<p className="h5 text-dark font-italic mb-0">
<span className='mr-1'>${price}</span>
</p>
</div>
</div>
</ProductWrapper>
)
}
Details component:
const Details = () => {
const CartValue = useCart();
const DetailValue = useDetail();
const {name, description, price, brand, image_link, rating} = DetailValue.detail;
return (
<div className="container py-5">
{/* product title */}
<div className="row">
<div className="col-10 mx-auto text-center text-slanted text-orange my5">
</div>
</div>
{/* end title */}
{/* product info */}
<div className="row">
<div className="col-10 mx-auto col-md-6 my-3">
<img src={image_link} className='img-fluid w-100' alt={name}/>
</div>
<div className="col-10 mx-auto col-md-6 my-3 text-capitalize">
<p className='h4'>{name}</p>
<div className='text-muted mt-3 mb-2'>
<p>brand: <span className='text-uppercase'>{brand}</span></p>
<p>rating: <span>{rating}</span></p>
</div>
<p className="h4 text-orange">
<strong>
price: <span>$</span>{price}
</strong>
</p>
<p className=" font-weight-bold mt-3 mb-0">
product description:
</p>
<p className="lead">
{description}
</p>
<div>
<Link to='/'>
<ButtonContainer>
Back to products
</ButtonContainer>
</Link>
<ButtonContainer onClick={() => addToCart(CartValue, DetailValue.detail)}>
Add To Cart
</ButtonContainer>
</div>
</div>
</div>
</div>
)
}
App structure:
<Route exact path="/" component={ProductList}/>
<Route path="/details" component={Details} />
<Route path="/cart" component={Cart} />
<Route path="/about" component={About} />
<Route path="/location" component={Location} />
State and hook placed in a separate file
import React, { createContext, useState, useContext } from 'react';
// creating contexts for global state
const ModalContext = createContext();
const DetailContext = createContext();
const CartContext = createContext();
// end of creating contexts
// custom hooks to access each state
export const useModal = () => {
return useContext(ModalContext);
}
export const useDetail = () => {
return useContext(DetailContext);
}
export const useCart = () => {
return useContext(CartContext);
}
// end of custom hooks
// Add to cart helper function
export const addToCart = (cart, item, modal) => {
const element = item;
const updateCart = () => cart.setCart(prev => Array.from(new Set([...prev, element])));
if(!modal){
updateCart();
} else {
updateCart();
modal.setModal({modalItem: element, modalOpen: true});
}
};
// GLOBAL STATE and wrapper for each state.
export const GlobalState = ({ children }) => {
const [modal, setModal] = useState({modalItem: {}, modalOpen: false});
const [detail, setDetail] = useState({});
const [cart, setCart] = useState([]);
return (
<ModalContext.Provider value={{modal,setModal}}>
<DetailContext.Provider value={{detail, setDetail}}>
<CartContext.Provider value={{cart, setCart}}>
{children}
</CartContext.Provider>
</DetailContext.Provider>
</ModalContext.Provider>
)
}
I have been attempting to toggle a class on click so that when I click on one of the mapped items in my Tasks component, I add the 'complete' class and put a line through that item (crossing items off of a todo list). However with my current code set up, when I click on one element to add the class, all the other elements get crossed out as well and vice versa.
Here is my current setup. The class 'complete' is what will add a line through one of the mapped items in the Tasks component.
import { Container, Row} from 'react-bootstrap';
import {Link} from 'react-router-dom';
import axios from 'axios';
const List = (props) =>{
return(
<div>
<Link style={{textDecoration:'none'}} to={`/lists/${props.listId}`} > <p className="list-item">{props.item}</p></Link>
</div>
)
}
const Tasks = (props) =>{
return(
<div onClick={props.onClick} className={props.className} >
<div className='task-item' >
<p >{props.item}</p>
</div>
</div>
)
}
export default class Display extends Component {
constructor(props){
super(props)
this.onCompletedTask = this.onCompletedTask.bind(this);
this.state = {
list: [],
tasks:[],
complete:false
}
}
componentWillUpdate(nextProps){
axios.get(`http://localhost:8080/lists/${this.props.match.params.listId}`)
.then(response =>{
this.setState({
tasks:response.data
})
})
}
componentDidMount(){
axios.get('http://localhost:8080/lists')
.then(response=>{
this.setState({
list:response.data
})
})
.catch(error =>{
console.log(error)
});
}
onCompletedTask(item){
this.setState({ complete: !this.state.complete});
}
listCollection(){
return(
this.state.list.map(item=>{
return(<List item = {item.title} listId={item._id} key = {item._id} />)
})
)
}
taskCollection(){
return(
this.state.tasks.map((item, index) =>{
return(<Tasks onClick = {()=>this.onCompletedTask(item)} className={this.state.complete ? 'complete': ''} item={item.task} key={index}/>)
})
)
}
render() {
return (
<div id='main' >
<Container>
<Row>
<div className="sidebar">
<h1 style={{fontSize:"25pt"}}>Lists</h1>
<div className="list-menu">
{this.listCollection()}
</div>
<form action='/new-list' method='GET'>
<div style={{textAlign:'center'}}>
<button className='list-button' style={{fontSize:'12pt', borderRadius:'5px'}}>
+ New List
</button>
</div>
</form>
</div>
<div className='tasks'>
<h1 style={{fontSize:'25pt'}}>Tasks</h1>
{this.taskCollection()}
<form action={`/lists/${this.props.match.params.listId}/new-task`} method='GET'>
<button className='task-button'>
+
</button>
</form>
</div>
</Row>
</Container>
</div>
)
}
}
Your state holds only a single completed value, which OFC toggle all tasks. You could instead store a map of completed tasks.
this.state = {
list: [],
tasks: [],
complete: {}, // <--- use empty object as simple map object
}
Update onCompletedTask to store some uniquely identifying property of a task, like an id field
onCompletedTask(item){
this.setState(prevState => ({
completed: {
...prevState.completed, // <--- spread existing completed state
[item.id]: !prevState.completed[item.id] // <--- toggle value
},
}));
}
Update. taskCollection to check the completed map by id
taskCollection = () => {
const { completed, tasks } = this.state;
return tasks.map((item, index) => (
<Tasks
onClick={() => this.onCompletedTask(item)}
className={completed[item.id] ? "complete" : ""} // <--- check completed[item.id]
item={item.task}
key={index}
/>
))
};