I am new in React.js and I am stuck somewhere from 3 days I try many solutions but nothing happens the problem is still there.
I have one parent component called checkout and its child component address there is a two function are bubbling which is handleSeleteAddress and handleDelete which I mention in the parent. handleSeleteAddress is working fine but when I try to delete the object from the array by using handleDelete function nothing happen kindly guide me how I can solve this
checkout.jsx(parent):
class Checkout extends Component {
state = {
address: [
{
id: 1,
title: "Home",
description: "27 Street, 2569 Heritage Road Visalia, CA 93291",
active: true,
},
{
id: 2,
title: "Office",
description: "27 Street, 2569 Heritage Road Visalia, CA 93291",
active: false,
},
],
};
getIndex = (value, arr, prop) => {
for (var i = 0; i < arr.length; i++) {
if (arr[i][prop] === value) {
return i;
}
}
return -1;
};
handleSeleteAddress = (index) => {
var getSeletedIndex = this.getIndex(true, this.state.address, "active");
const getSelectedObject = [...this.state.address];
getSelectedObject[getSeletedIndex].active = false;
getSelectedObject[index].active = true;
this.setState({ address: getSelectedObject });
};
handleDelete = (addressId) => {
const getSelectedObject = this.state.address.filter(
(e) => e.id !== addressId
);
this.setState({ address: getSelectedObject });
};
render() {
return (
<React.Fragment>
<div class="container">
<div class="row">
<div class="col-sm-6 text-center">
<AddressSection
address={this.state.address}
addressClass={this.handleAddressClass}
seleteAddress={this.handleSeleteAddress}
deleteAddress={this.handleDelete}
/>
</div>
<div class="col-sm-6 text-center">asd</div>
</div>
</div>
</React.Fragment>
)
}
}
AddressSection.jsx(Child):
<div className="addressBoxes">
{this.props.address.map((div, index) => (
<div
onClick={() => this.props.seleteAddress(index)}
className={this.props.addressClass(index)}
>
<p>
<b>{div.title}</b>
</p>
<p>{div.description}</p>
<div className="edit">
<span
className="editBtn"
onClick={() => this.showEditRodal(index)}
>
<img src="./assets/images/edit.png" />
</span>
<span
className="deleteBtn"
onClick={() => this.props.deleteAddress(div.id)}
>
<img src="./assets/images/check.gif" />
</span>
</div>
</div>
))}
</div>
You should pass event from AddressSection.jsx(Child) and stop propagation :)
handleDelete = (e, addressId) => {
e.stopPropagation();
const getSelectedObject = this.state.address.filter(
(e) => e.id !== addressId
);
this.setState({ address: getSelectedObject });
};
Related
I am working on a page
have two sections. In one section we upload image and can increase the number of input, in the other section we will preview the image.
I don't know I am not getting the right id that I want to edit.
we can add more fields on click of button and remove then at the end post data at api endpoint?
RightSection
const RightSection = ({ inputImage, setInputImage }) => {
const handleAddFields = () => {
if (inputImage.length <= 9) {
const values = [...inputImage];
values.push({
id: values.length + 1,
name: `Drop Image ${values.length + 1} Here`,
});
setInputImage(values);
}
};
const handleRemoveFields = () => {
if (inputImage.length > 3) {
const values = [...inputImage];
values.splice(values.length - 1, 1);
setInputImage(values);
}
};
const handleInputChange = (id, event) => {
console.log(id, event.target.id, "=====");
const newInputFields = inputImage.map((i) => {
if (id === i.id) {
i.url = URL.createObjectURL(event.target.files[0]);
i.name = event.target.files[0].name;
// push image object in array
setInputImage([
...inputImage,
{
id: id,
url: URL.createObjectURL(event.target.files[0]),
name: event.target.files[0].name,
},
]);
}
return i;
});
setInputImage(newInputFields);
};
console.log(inputImage);
return (
<>
<div id="right" className="flex">
<div className="margin">
<div className="inlineflex">
<H1>Background Image</H1>
<div>
<AddIcon onClick={handleAddFields} />
<RemoveIcon onClick={handleRemoveFields} />
</div>
</div>
</div>
<div
style={{
margin: "0 auto",
position: "relative",
width: "80%",
}}
>
{inputImage.map((inputField, index) => (
<FileInput key={index}>
<label
htmlFor={inputField.id}
onClick={(e) => {
console.log("click", index + 1);
}}
>
{inputField.name}
</label>
<input
id={index + 1}
onChange={(event) => handleInputChange(inputField.id, event)}
accept="image/*"
type="file"
/>
</FileInput>
))}
</div>
</div>
</>
);
};
LeftSection
const LeftSection = ({ inputImage }) => {
return (
<>
<div id="left" className="flex">
<div className="margin">
<H1>Preview</H1>
</div>
<Grid>
{Array.isArray(inputImage) &&
inputImage.map((item, index) => {
if (item?.url?.includes("http") || item?.url?.includes("https")) {
return (
<div key={index}>
<img src={item?.url} alt={item?.name} />
</div>
);
}
})}
</Grid>
</div>
</>
);
};
BackgroundImage
let initaValue = [
{ id: 1, name: "Drop Image 1 Here", url: "" },
{ id: 2, name: "Drop Image 2 Here", url: "" },
{ id: 3, name: "Drop Image 3 Here", url: "" },
];
const BackgroundImage = () => {
const [inputImage, setInputImage] = useState(initaValue);
return (
<>
<Container>
<RightSection inputImage={inputImage} setInputImage={setInputImage} />
<LeftSection inputImage={inputImage} setInputImage={setInputImage} />
</Container>
</>
);
};
export default BackgroundImage;
I think there is some issue with the handleInputChange function on the RightSection component.
Somehow I am not able to update the item with the correct id in the array.
Is there any other efficient solution for this problem?
I've created script for dynamic form, but there's 2 things which I can't get and my head is exploding right now, hopefully somebody would help me with that.
After creating new fields - I can't remove fields depends on button which was clicked.
And after removing some of those fields, I have this error with fieldsenter image description here
import React from "react";
import {useState , useEffect} from "react";
import ReactDOM from "react-dom";
import "./index.css";
const Form = () =>{
const [fieldsLength, fieldsLengthChanger] = useState(1);
const [fields, fieldsChanger] = useState([{
id : 1,
name: "",
phone: "",
age: ""
}])
return (
<>
<div className="form__wrapper">
<h2>Form </h2>
{
fields.map((elem, index) => {
return(
<FormElement {...elem} fields={fields} fieldsChanger={fieldsChanger} fieldsLength={fieldsLength} fieldsLengthChanger={fieldsLengthChanger}/>
)
})
}
<AddMore fieldsLength={fieldsLength} fieldsLengthChanger={fieldsLengthChanger} fields={fields} fieldsChanger={fieldsChanger}/>
</div>
</>
)
}
const FormElement = ({fieldsLength ,...props}) =>{
function inputHandler(e, id){
console.log(e.target.name);
const values = [...props.fields];
values[id-1][e.target.name] = e.target.value;
props.fieldsChanger(values);
}
function removeElement(e,id){
e.preventDefault();
var arr = [...props.fields];
const newArray = arr.filter(function(elem,index){
console.log("index:" , index, "ID : ", id);
if (index + 1 != id){
return elem;
}
});
// console.log(newArray);
// arr.splice(3, 1);
props.fieldsChanger([])
props.fieldsChanger(newArray);
}
return (
<div className="group__form">
<div className="form__element">
<input type="text" value={props.fields.name} name="name" onChange={e => inputHandler(e , props.id)} />
</div>
<div className="form__element">
<input type="text" value={props.fields.phone} name="phone" onChange={e => inputHandler(e , props.id)}/>
</div>
<div className="form__element">
<input type="text" value={props.fields.age} name="age" onChange={e => inputHandler(e , props.id)}/>
</div>
{
fieldsLength > 1 ? <div className="remove__field">
<a href="#" onClick={e=>removeElement(e , props.id)}>Remove</a>
</div> : ""
}
</div>
)
}
const AddMore = (props) =>{
function addMore(){
props.fieldsLengthChanger(props.fields.length + 1);
props.fieldsChanger([...props.fields, {id:props.fields.length + 1 , name: "" , phone : "" , age :''} ]);
}
return (
<div className="add__more">
<a href="#" onClick={e=> addMore()}>Add element</a>
</div>
)
}
ReactDOM.render(<Form/> , document.getElementById("root"));
Where I'm wrong - would be really helpfull to understand what is the problem
Always use key when rendering list.
<FormElement key={elem.id} {...elem} ...
https://reactjs.org/docs/lists-and-keys.html#keys
1. You should use the props.id to remove a form instead of using index.
e.g. this must be a bug.
if (index + 1 != id) {
return elem;
}
2. And you should use id by the unique value generation instead of using array's length.
3. You should provide the key prop in the render of the form fields. Otherwise, React can't distinguish forms in rendering.
And the key should be unique whenever you add or remove items.
e.g. See the updated code. You need to use elem.id. (Of course, you should generate id in the add method.
{fields.map((elem, index) => {
return (
<FormElement
key={elem.id}
{...elem}
fields={fields}
fieldsChanger={fieldsChanger}
fieldsLength={fieldsLength}
fieldsLengthChanger={fieldsLengthChanger}
/>
);
})}
4. You could basically use a timestamp. Please use your own generation logic in the production. You can use uuid generation package.
{
id: new Date().getTime(),
name: "",
phone: "",
age: ""
}
My advices:
You don't need to use this state variable const [fieldsLength, fieldsLengthChanger] = useState(1);
Because we can get this value by fields.length.
There are a few problems in terms of components design. e.g. Please try to define addMore() in form then you don't need to pass fields, fieldsChanger as props.
Here is a full working code with your old code commented.
const Form = () => {
const [fieldsLength, fieldsLengthChanger] = useState(1);
const [fields, fieldsChanger] = useState([
{
id: new Date().getTime(),
name: "",
phone: "",
age: ""
}
]);
return (
<>
<div className="form__wrapper">
<h2>Form </h2>
{fields.map((elem, index) => {
return (
<FormElement
key={elem.id}
{...elem}
fields={fields}
fieldsChanger={fieldsChanger}
fieldsLength={fieldsLength}
fieldsLengthChanger={fieldsLengthChanger}
/>
);
})}
<AddMore
fieldsLength={fieldsLength}
fieldsLengthChanger={fieldsLengthChanger}
fields={fields}
fieldsChanger={fieldsChanger}
/>
</div>
</>
);
};
const FormElement = ({ fieldsLength, ...props }) => {
function inputHandler(e, id) {
console.log(e.target.name);
const values = [...props.fields];
const item = values.find((x) => x.id === props.id);
if (item) {
item[e.target.name] = e.target.value;
}
// values[id - 1][e.target.name] = e.target.value;
props.fieldsChanger(values);
}
function removeElement(e, id) {
e.preventDefault();
var arr = [...props.fields];
const newArray = arr.filter(function (elem, index) {
/*console.log("index:", index, "ID : ", id);
if (index + 1 != id) {
return elem;
}*/
return elem.id !== id;
});
// console.log(newArray);
// arr.splice(3, 1);
props.fieldsChanger([]);
props.fieldsChanger(newArray);
}
return (
<div className="group__form">
<div className="form__element">
<input
type="text"
value={props.fields.name}
name="name"
onChange={(e) => inputHandler(e, props.id)}
/>
</div>
<div className="form__element">
<input
type="text"
value={props.fields.phone}
name="phone"
onChange={(e) => inputHandler(e, props.id)}
/>
</div>
<div className="form__element">
<input
type="text"
value={props.fields.age}
name="age"
onChange={(e) => inputHandler(e, props.id)}
/>
</div>
{fieldsLength > 1 ? (
<div className="remove__field">
<a href="#" onClick={(e) => removeElement(e, props.id)}>
Remove
</a>
</div>
) : (
""
)}
</div>
);
};
const AddMore = (props) => {
function addMore() {
props.fieldsLengthChanger(props.fields.length + 1);
props.fieldsChanger([
...props.fields,
//{ id: props.fields.length + 1, name: "", phone: "", age: "" }
{ id: new Date().getTime(), name: "", phone: "", age: "" }
]);
}
return (
<div className="add__more">
<a href="#" onClick={(e) => addMore()}>
Add element
</a>
</div>
);
};
Why does ReactJS remove the last element when the array is different after removing the middle element when using array.splice?
This is my code. I am using React-Redux.
const reducerNotesAndLogin = (state = initialState, action) => {
var tableNotes = "notities";
var tableCategories = "categories";
switch(action.type){
case "CATEGORY_REMOVE":
// Remove the category
var newCategories = state.categories;
console.log("state.categories", state.categories);
console.log("before: ", {newCategories});
var index = 0;
for(var i = 0; i < newCategories.length; i++){
if(newCategories[i].id === action.payload.categoryId){
newCategories.splice(i, 1);
index = i;
i--;
}
}
console.log("after: ", {newCategories});
state = {
...state,
categories: newCategories
}
break;
default:
break;
}
return state;
}
export default reducerNotesAndLogin;
Output below (I deleted the middle element. My web app always removes the last element of the categories (but not from the array).
Step 1: Initial state
Step 2: Remove middle item, expecting the middle item to be removed.
Step 3: Confusion
Why is the array correct, but the view incorrect? I am updating the state.categories correctly right?
This is my render code (as is - without filtering away any other code that mihgt be important)
CategoriesBody:
import React from 'react';
import { connect } from 'react-redux';
import CategoryItem from './CategoryItem';
import Button from './../../Button';
import store from '../../../redux/store-index';
class CategoriesBody extends React.Component {
render(){
return (
<div>
<ul className="list--notes">
{this.props.categories.map((category) => {
if(category.id === undefined){ // No categories
return <li>No categories</li>
} else {
return (
<div>
<CategoryItem category={category} />
<div className="mb-small hidden-sm hidden-md hidden-lg"> </div>
</div>
);
}
})}
</ul>
</div>
);
}
}
function mapStateToProps(state){
return {
categories: state.reducerNotesAndLogin.categories,
categoriesLength: state.reducerNotesAndLogin.categories.length
};
}
export default connect(mapStateToProps)(CategoriesBody);
CategoriesItem.js:
import React from 'react';
import store from './../../../redux/store-index';
import Button from './../../Button';
class CategoryItem extends React.Component {
constructor(props){
super();
this.state = {
edit: false,
categoryName: props.category.categoryName,
categoryColor: props.category.categoryColor
}
this.onClickEdit = this.onClickEdit.bind(this);
this.onChangeCategoryColor = this.onChangeCategoryColor.bind(this);
this.onChangeInputCategoryName = this.onChangeInputCategoryName.bind(this);
this.onClickEditSave = this.onClickEditSave.bind(this);
this.onClickEditCancel = this.onClickEditCancel.bind(this);
}
removeCategory(id, name){
console.log("nsvbsvbfjvbdjhbvv");
store.dispatch({ type: "CATEGORY_REMOVE", payload: {
categoryId: id
}});
// store.dispatch({type: "NOTIFY", payload: {
// type: 'success',
// message: 'Category "' + name + '" removed!'
// }});
}
onClickEdit(){
this.setState({
edit: true
});
}
onChangeCategoryColor(e){
this.setState({
categoryColor: e.target.value
});
}
onChangeInputCategoryName(e){
this.setState({
categoryName: e.target.value
});
}
onClickEditSave(){
this.setState({
edit: false,
categoryName: this.state.categoryName,
categoryColor: this.state.categoryColor
});
store.dispatch({type: "CATEGORY_EDIT", payload: {
categoryId: this.props.category.id,
categoryName: this.state.categoryName,
categoryColor: this.state.categoryColor
}});
store.dispatch({type: "NOTIFY", payload: {
type: "success",
message: "Category saved!"
}});
}
onClickEditCancel(){
this.setState({
edit: false,
categoryName: this.props.category.categoryName,
categoryColor: this.props.category.categoryColor
});
}
render(){
return (
<li key={this.props.category.id} className={this.state.edit === true ? "mt mb" : "flex-justify-between flex-align-center"}>
<div className={this.state.edit === true ? "d-none" : ""}>
<div className="input--color" style={{
backgroundColor: this.state.categoryColor
}}> </div>
{this.state.categoryName}
</div>
{/* Mobile */}
<div className={this.state.edit === true ? "d-none" : "hidden-sm hidden-md hidden-lg"}>
<Button onClick={() => this.onClickEdit()} buttonType="primary">Edit</Button>
<div className="mt-small"> </div>
<Button onClick={() => this.removeCategory(this.props.category.id, this.props.category.categoryName)} type="primary">Remove</Button>
</div>
{/* Tablet and desktop */}
<div className={this.state.edit === true ? "d-none" : "hidden-xs"}>
<div style={{float:'left',}}><Button onClick={() => this.onClickEdit()} buttonType="primary">Edit</Button></div>
<div style={{float:'left',marginLeft:'15px'}}><Button onClick={() => this.removeCategory(this.props.category.id, this.props.category.categoryName)} type="primary">Remove</Button></div>
</div>
{/* EDITING STATE */}
<div className={this.state.edit === true ? "" : "d-none"}>
<div className="row">
<div className="col-xs-12">
<input onChange={this.onChangeCategoryColor} className="input--wide" type="color" value={this.state.categoryColor}
style={{backgroundColor: this.state.categoryColor, height: '30px'}}
/>
<input onChange={this.onChangeInputCategoryName} className="input--wide" type="text" value={this.state.categoryName} />
</div>
</div>
<div className="row mt">
<div className="col-xs-12">
<Button buttonType="primary" onClick={() => this.onClickEditSave()}>Save</Button>
</div>
</div>
<div className="row mt-small">
<div className="col-xs-12">
<Button buttonType="secondary" onClick={() => this.onClickEditCancel()}>Cancel</Button>
</div>
</div>
</div>
</li>
)
}
}
export default CategoryItem;
I think it has something to do with the rendering. Because the arrays are correct when I console.log them. Only the view is different...
Do not modify the state in reducer directly. Create a copy of state value and then modify it.
Change:
var newCategories = state.categories;
To:
var newCategories = [...state.categories];
You should not modify the same array while looping through it.
for (var i = 0; i < newCategories.length; i++) {
if (newCategories[i].id === action.payload.categoryId) {
newCategories.splice(i, 1);
index = i;
i--;
}
}
I got the answer after looking through it with a friend of mine. The solution is pretty simple...
Lesson 101: Make sure that you have a unique "key" property when looping through an array in your UI.
The solution is to add this to my code:
<div key={category.id}>
{this.props.categories.map....
...
</div>
Sorry for my English)
Do not judge strictly, since I just started working with the react.
I made a simple slider on the react and now I want to make it cyclic.
But I can’t. In my code it seems to be cyclic, but for some reason it skips the last picture.
how can i fix it?
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [
{
id: 1,
name: "Product 1",
price: 50,
q: 0,
category: "Sporting Goods",
images: [
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property04.jpg",
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property02.jpg",
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property01.jpg",
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property03.jpg"
],
currentImageIndex: 0,
isCycleMode: false,
cantGoPrev: false,
cantGoNext: true
},
{
id: 2,
name: "Product 2",
price: 70,
q: 0,
category: "Sporting Goods",
images: [
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property04.jpg",
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property02.jpg",
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property01.jpg",
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property03.jpg",
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property02.jpg",
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property01.jpg",
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property03.jpg"
],
currentImageIndex: 0,
isCycleMode: false,
cantGoPrev: false,
cantGoNext: true
}
]
};
}
nextSlideHandler = (e, item, index ) => {
let arr = [...this.state.data];
let newIndex = this.state.data[index].currentImageIndex;
if (e.currentTarget.dataset.direction === "next") {
if (newIndex < this.state.data[index].images.length - 1) {
newIndex = this.state.data[index].currentImageIndex + 1;
arr[index].cantGoPrev = true;
this.setState({data:arr})
}
if (newIndex === this.state.data[index].images.length - 1) {
newIndex = 0;
arr[index].cantGoNext = true;
this.setState({data:arr})
}
} else {
if (newIndex > 0) {
newIndex = this.state.data[index].currentImageIndex - 1;
arr[index].cantGoNext = true;
this.setState({data:arr})
}
if (newIndex === 0) {
arr[index].cantGoPrev = false;
this.setState({data:arr})
}
}
arr[index].currentImageIndex = newIndex;
this.setState({ data:arr });
}
render() {
return (
<div className="App">
<div>
<h3>Products</h3>
<div className="collection">
{this.state.data.map((item, index) => (
<div key={item.id} className="product">
<div className="product__image">
<div>
<button
disabled={!item.cantGoPrev}
data-direction="prev"
onClick={(e)=> this.nextSlideHandler(e,item, index)}
className="prev"
>
prev
</button>
</div>
<div>
<img
src={item.images[item.currentImageIndex]}
alt=""
/>
{item.images.currentImageIndex}
</div>
<div>
<button
disabled={!item.cantGoNext}
data-direction="next"
onClick={(e)=> this.nextSlideHandler(e, item, index)}
className="next"
>
next
</button>
</div>
</div>
<div className="product__name">{item.name}</div>
<div className="product__price">{item.price}</div>
</div>
))}
</div>
</div>
</div>
);
}
}
What is the best way to write a slider?
I will be glad of any help
First: There are many ways to achieve what you are trying to do, but this is how I would have done it.
Instead of using index to update different state, I would make a own component for Product. Inside that component you have full access to that products state and props. This makes it easier to work with the correct data.
I would also remove the next/previous-logic from the state and just do a check if the buttons should be active on render.
class App extends React.Component {
constructor (props) {
super(props)
this.state = {
data: [
{
id: 1,
name: 'Product 1',
price: 50,
q: 0,
category: 'Sporting Goods',
images: [
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property04.jpg',
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property02.jpg',
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property01.jpg',
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property03.jpg'
],
currentImageIndex: 0,
isCycleMode: false
},
{
id: 2,
name: 'Product 2',
price: 70,
q: 0,
category: 'Sporting Goods',
images: [
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property04.jpg',
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property02.jpg',
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property01.jpg',
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property03.jpg',
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property02.jpg',
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property01.jpg',
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property03.jpg'
],
currentImageIndex: 0,
isCycleMode: false
}
]
}
}
handleChange = (arr) => {
// State updates based on other state should be asynchronous
// https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
this.setState((state, props) => {
const oldArr = [...state.data]
const arrIndex = oldArr.findIndex(x => x.id === arr.id)
oldArr[arrIndex] = arr
return ({
data: oldArr
})
})
}
render () {
return (
<div className='App'>
<div>
<h3>Products</h3>
<div className='collection'>
{this.state.data.map((item) => (
<Product
item={item}
key={item.id}
onChange={this.handleChange}
/>
))}
</div>
</div>
</div>
)
}
}
class Product extends React.Component {
handleSlideChange = (e) => {
const arr = { ...this.props.item }
if (e.currentTarget.dataset.direction === 'next') {
arr.currentImageIndex++
} else {
arr.currentImageIndex--
}
this.props.onChange(arr)
};
render () {
const { item } = this.props
return (
<div key={item.id} className='product'>
<div className='product__image'>
<div>
<button
disabled={item.currentImageIndex <= 0}
data-direction='prev'
onClick={this.handleSlideChange}
className='prev'
>
Prev
</button>
</div>
<div>
<img src={item.images[item.currentImageIndex]} alt='' />
{item.images.currentImageIndex}
</div>
<div>
<button
disabled={item.currentImageIndex >= item.images.length - 1}
data-direction='next'
onClick={this.handleSlideChange}
className='next'
>
Next
</button>
</div>
</div>
<div className='product__name'>{item.name} {item.currentImageIndex}</div>
<div className='product__price'>{item.price}</div>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I have a class component(actually the collection of the same components) where I have 2 buttons + and - to increase and decrease quantity of watches. Min amount of watches is 1 and max amount is 10. I have regulated this with this 2 functions increaseAmountHandler and decreaseAmountHandler. With this two buttons it's all ok. But the problem is that I have to sum up the value of calculated watches in parent component and I cannot forward the summed up values of the watches to a parent component to state variable totalAmount. Cannot use Redux becacuse it's a collection of watches component and each have own + and - button already occupied with this 2 increaseAmountHandler, decreaseAmountHandler functions.
Anyone idea how to solve this?
Child component:
import React, { Component } from 'react';
import Modal from '.././UI/Modal';
class SelectedWatch extends Component {
constructor(props) {
super(props)
this.state = {
watchQuantity: 1,
watchAmount: 1
}
}
increaseAmountHandler = () => {
if(this.state.watchQuantity < 1) {
this.setState({
watchQuantity: 0,
watchAmount: 0
})
return;
} else if (this.state.watchQuantity >= 10){
this.setState({
watchQuantity: 10,
watchAmount: this.props.selectedWatch.selectedWatchPrice * this.state.watchQuantity
})
return;
}
this.setState({
watchQuantity: this.state.watchQuantity + 1,
watchAmount: this.props.selectedWatch.selectedWatchPrice * this.state.watchQuantity
})
}
decreaseAmountHandler = () => {
if(this.state.watchQuantity < 1) {
this.setState({
watchQuantity: 0,
watchAmount: 0
})
return;
} else if (this.state.watchQuantity >= 10){
this.setState({
watchQuantity: 9,
watchAmount: this.props.selectedWatch.selectedWatchPrice * this.state.watchQuantity
})
return;
}
this.setState({
watchQuantity: this.state.watchQuantity - 1,
watchAmount: this.props.selectedWatch.selectedWatchPrice * this.state.watchQuantity
})
}
render() {
return (
<div className={"shopping-cart-product" + (this.state.watchQuantity < 1 ? ' notDisplayed' : '')}>
<div className="product-info">
<div>
<h3>{this.props.selectedWatch.selectedWatchName}</h3>
<p>${this.props.selectedWatch.selectedWatchPrice} × {this.state.watchQuantity}</p>
</div>
<img src={this.props.selectedWatch.selectedWatchUrl} />
</div>
<div className="product-count">
<button onClick={this.decreaseAmountHandler}>-</button>
<span>{this.state.watchQuantity}</span>
<button onClick={this.increaseAmountHandler}>+</button>
</div>
</div>
);
}
}
export default SelectedWatch;
Parent component:
import React, { Component } from 'react';
import EnteredWatch from '.././components/EnteredWatch/EnteredWatch';
import SelectedWatch from '.././components/SelectedWatch/SelectedWatch';
class App extends Component {
constructor(props) {
super(props)
this.state = {
watchName: '',
watchDescription: '',
watchUrl: '',
watchPrice: '',
watchId: '',
watchAmount: '',
watchQuantity: 1,
enteredWatchList: [],
selectedWatchName: '',
selectedWatchDescription: '',
selectedWatchUrl: '',
selectedWatchPrice: '',
selectedWatchId: '',
selectedWatchAmount: '',
selectedWatchQuantity: 1,
selectedWatchList: [],
totalAmount: 0,
}
}
submitHandler = (event) => {
event.preventDefault();
let watchId = Math.floor((Math.random() * 100) + 1);
let watchName = this.state.watchName;
let watchDescription = this.state.watchDescription;
let watchUrl = this.state.watchUrl;
let watchPrice = this.state.watchPrice;
let watchQuantity = this.state.watchQuantity;
this.setState({
enteredWatchList: this.state.enteredWatchList.concat({watchName, watchUrl, watchDescription, watchPrice, watchId, watchQuantity})
})
add = (selectedWatchName, selectedWatchUrl, selectedWatchDescription, selectedWatchPrice, index, selectedWatchQuantity) => {
let arr = this.state.selectedWatchList;
let found = arr.some(el => {
return el.selectedWatchName === selectedWatchName;
});
if (!found) {
return arr.concat({selectedWatchName, selectedWatchUrl, selectedWatchDescription, selectedWatchPrice, index, selectedWatchQuantity});
} else {
return this.state.selectedWatchList;
}
}
buyWatchHandler = (selectedWatchName, selectedWatchUrl, selectedWatchDescription, selectedWatchPrice, index, selectedWatchQuantity) => {
let arr = this.add(selectedWatchName, selectedWatchUrl, selectedWatchDescription, selectedWatchPrice, index, selectedWatchQuantity);
this.setState({
selectedWatchName: selectedWatchName,
selectedWatchUrl: selectedWatchUrl,
selectedWatchDescription: selectedWatchDescription,
selectedWatchPrice: selectedWatchPrice,
selectedWatchId: index,
selectedWatchQuantity: selectedWatchQuantity,
selectedWatchList: arr
});
}
render() {
const enteredWatches = this.state.enteredWatchList.map((enteredWatch, index) => {
return <EnteredWatch
key={index}
enteredWatch={enteredWatch}
selected={this.buyWatchHandler.bind(this, enteredWatch.watchName, enteredWatch.watchUrl,
enteredWatch.watchDescription, enteredWatch.watchPrice, index, enteredWatch.watchQuantity)}
/>
});
const selectedWatches = this.state.selectedWatchList.map((selectedWatch, index) => {
const active = this.state.activeIndex;
return <SelectedWatch
key={index}
active={index === active}
selectedWatch={selectedWatch}
/>
});
return (
<div className="App">
<div className="container-fluid">
<div className="container">
<div className="add-product">
<form>
<div>
<label>Product name:</label>
<input
type="text"
placeholder="Casio Watch"
required
value={this.state.watchName}
onChange={event => this.setState({watchName: event.target.value})}
/>
</div>
<div>
<label>Product description:</label>
<textarea
placeholder="Sample description..."
value={this.state.watchDescription}
onChange={event => this.setState({watchDescription: event.target.value})}
>
</textarea>
</div>
<div>
<label>Product image:</label>
<input
type="text"
placeholder="http://...jpg"
value={this.state.watchUrl}
pattern="https?://.+" required
onChange={event => this.setState({watchUrl: event.target.value})}
/>
</div>
<div>
<label>Product price:</label>
<input
type="number"
min="0"
placeholder="22"
value={this.state.watchPrice}
onChange={event => this.setState({watchPrice: event.target.value})}
/>
</div>
<button
type="submit"
onClick={event => this.submitHandler(event)}
>
Add a new Task
</button>
</form>
</div>
<div className="list-products">
<ul>
{enteredWatches}
</ul>
</div>
<div className="shopping-cart">
<div className="shopping-cart-products">
<ul>
{selectedWatches}
</ul>
</div>
<div className="shopping-cart-summary">
<div>Total: <b>${this.state.totalAmount}</b></div>
<div><button onClick={this.summaryHandler}>Purchase</button></div>
</div>
</div>
</div>
</div>
</div>
);
}
}
export default App;
The parent has to keep track of how many watches have been added.
Make the parent smart (has state), and the children dumb (no state).
Manage all the state in the parent, and put the click handlers in the parent too.
Pass those handlers down to the child, to be fired when its buttons are clicked. Something like this:
class Parent extends React.Component {
this.state = {
cart: [],
watches: [
{ id: 1, name: "Casio", description: "...", price: 25 },
{ id: 2, name: "Rolex", description: "...", price: 3000 },
{ id: 3, name: "Timex", description: "...", price: 10 },
],
}
handleClickIncrement = (watchId) => {
//add it to the cart (or increment it if its already there)
}
handleClickDecrement = (watchId) => {
//remove it from the cart (or deccrement it if its already there)
}
getCartTotal() {
//loop over cart and calculate
}
renderWatches() {
this.state.watches.map(watch => (
<Watch id={watch.id}
name={watch.name}
description={watch.description}
price={watch.price}
onClickIncrement={() => { this.handleClickIncrement(watch.id); }}
onClickDecrement={() => { this.handleClickDecrement(watch.id); }}
))
}
render() {
<div>
<h1>Our selection of watches:</h1>
{this.renderWatches()}
<h1>Your cart total: {this.getCartTotal()}</h1>
</div>
}
}
class Watch extends React.Component {
props = {
id,
name,
description,
price,
quantityInCart,
onClickIncrementButton,
onClickDecrementButton
}
render() {
<div>
<h1>{this.props.name}</h1>
<p>{this.props.description}</p>
<h5>${this.props.price}</h5>
<button onClick={this.props.onClickIncrementButton}>+</button>
<button onClick={this.props.onClickDecrementButton}>-</button>
</div>
}
}