React remove element from onclick - javascript

When a user deletes an item from their cart, I have the item displayed with a button to add it back to the cart. This works. Once the user adds the item back to their cart, I want the item in the display component to be removed. Here is my code for reference.
CART:
class Cart extends Component {
constructor(props) {
super(props);
this.state = {differences: [],};
}
componentWillReceiveProps(nextProps){
let thisProps = this.props.cart.items;
let theNextProps = nextProps.cart.items;
if (thisProps.map(i => i.sku).some(item => !theNextProps.map(i => i.sku).includes(item))) {
let diff = [thisProps.filter(item => !theNextProps.includes(item))];
this.setState({differences: this.state.differences.concat(diff)});
}
}
...
render = () => {
<CartAddBack data={this.state.differences} onAddToCart={this.props.addToCart} />
<CheckoutSection className='Checkout-cart-items' titleKey='checkout.items.title'>
{this.props.cart.items.map((item) => {
return (
<CheckoutItem item={item} key={item.sku} onRemoveProduct={this.props.removeFromCart} onUpdateQuantity={this.props.updateCartItem}/>
);
})}
</CheckoutSection>
}
}
CartAddBack:
class CartAddBack extends Component {
constructor() {
super();
this.state = {deleted: null};
this.onDelete = this.onDelete.bind(this);
}
onDelete(id){
console.log("THE SKU SHOULD BE HERE", id);
this.setState(id);
}
render() {
let {data} = this.props;
let theData = data.map(i => parseInt(i[0].sku));
let theStated = this.state.deleted;
return (
<div>
{data &&
<div className="CartAddBack">
<div className="CartAddBack-Wrapper">
<ul className="CartAddBack-Item-ul">
{theStated != null
? theData.filter(i => !theStated.includes(i)) &&
<CartAddBackItem data={item[0]} onAddToCart={this.props.onAddToCart} onDelete={this.onDelete}/>
: data.map((item) => {
return <CartAddBackItem data={item[0]} onAddToCart={this.props.onAddToCart} onDelete={this.onDelete}/>
})
}
</ul>
</div>
</div>
}
</div>
)
}
}
CartAddBackItem:
class CartAddBackItem extends Component {
constructor() {
super();
this.onClick = this.onClick.bind(this);
}
onDelete(){
this.props.onDelete({deleted: this.props.data.sku})
}
allowSubmit() {
this.setState({
allowSubmit: true,
});
}
onClick() {
if (this.props.data) {
if (this.props.data.quantity <= this.props.data.inventory_quantity) {
const success = () => {
this.allowSubmit();
},
failure = (err) => {...};
this.props.onAddToCart({
...{sku: this.props.data.sku, quantity: this.props.data.quantity}, quantity: this.props.data.quantity}).then(success, failure);
}
else {
this.setState=({display: false});
const success = () => {
this.allowSubmit();
},
failure = (err) => {...};
this.props.onAddToCart({
...{sku: this.props.data.sku, quantity: this.props.data.quantity}, quantity: 1}).then(success, failure);
}
}
}
render() {
let {data} = this.props;
return (
<li className="CartAddBackItem">
{data &&
<div className="CartAddBackItem-Wrapper">
<Button className="CartAddBackItem-button" onClick={this.onClick}><FormattedMessage id="cart.cartAddBack"/></Button>
<Link to={`product/${data.sku}`} className="CartAddBackItem-Link">
<p className="CartAddBackItem-title">{data.title}</p>
</Link>
</div>
}
</li>
)
}
}
I want CartAddBack to remove CartAddBackItem if the item was clicked in CartAddBackItem. Only thing I havent tried that I just thought about was to make a componentWillReceiveProps inside CartAddBack. But there has to be a better way. Issue I'm running into is my mapping items into CartAddBackItem. The gross looking {theStated != Null ? theData.filter(i =>... allows me to add items to the cart. It works if it was only data.map((item)=>... but I want to show my thinking. Any advice?

Related

Parent scope not triggering child rerender in React

i have a prent comp and a child cmponent. as follows
parent
export class ExpressionMenu extends Component {
constructor(props) {
super(props)
}
state = {
apiArray: [],
}
updateStateFromChild = (arrayType, propertyType, value) => {
let { apiArray } = this.state
let currentArray = []
let idx = apiArray.findIndex((q) => q.id === id)
currentArray = apiArray
switch(propertyType) {
case 'updateObject': {
currentArray = value
break;
}
}
this.setState({
apiArray: currentArray
})
}
render () {
const {
apiArray
} = this.state
return (
<React.Fragment>
<div >
<div>
<ApiPanel
apiArray={apiArray}
updateStateFromChild={this.updateStateFromChild}
/>
</div>
</div>
</React.Fragment>
)
}
}
ExpressionMenu.propTypes = {
styleOverride: PropTypes.object,
eventHandler: PropTypes.func,
};
export default ExpressionMenu;
child
export class ApiPanel extends Component {
constructor(props) {
super(props),
}
removeApi = (id) => {
let { apiArray } = this.props
apiArray = apiArray.filter((q) => q.id !== id);
this.props.updateStateFromChild('api', 'updateObject', apiArray)
};
addApi = () => {
let { apiArray } = this.props
const id = uniqid();
let obj = {}
obj.id = id
apiArray.push(obj)
this.props.updateStateFromChild('api', 'updateObject', apiArray)
};
render() {
const { apiArray } = this.props
return (
<React.Fragment>
{
apiArray.map((apiObj, i) =>
<div key={i} >
<span onClick={() => this.removeApi(apiObj.id) } className={[classes.deleteRow,'material-icons'].join(' ')}>
close
</span>
<div>
<label><b>Hi</b></label>
</div>
<div onClick={this.addApi}>+Add Api</div>
}
</React.Fragment>
)
}
}
ApiPanel.propTypes = {
apiArray: PropTypes.array,
updateStateFromChild: PropTypes.func
}
export default ApiPanel
Now when i call addApi(), it updates the parent but doesnt rerenders the child.
But when i call removeApi() , it updates parent as well as rerenders the child component properly.
in the first case when i manually reload the componnt i can see the change.
Dont understand why this is happening
Try to change your addApi function.
addApi = () => {
let { apiArray } = this.props
this.props.updateStateFromChild('api', 'updateObject', [...apiArray, {id : uniqid()} ])
};
You need to return an enriched copy of your array
Whenever we are updating the stating using arrays, objects. We need to always create a new array [...array], a new object {...obj}. Because if we update value in the array or obj without creating it won't change the reference value hence it assumes the state is not update and won't re-render.

index.js:1 Warning: Cannot update during an existing state transition. Render methods should be a pure function of props and state

I have been seeing solutions in other questions, such as placing anonymous function to the onClick, removing things from the render, but I keep getting the same problem, the file that marks me where the problem is is the ProductCard.tsx, which has this:
interface PastRouteinfo {
route: string
paymentMethod?: string
}
interface IProps {
settings?: Settings
product: Product
onProductClick?: (product: Product) => void
onGoToCart?: () => void
tracking?: any
cartModel: CartModel
hidePrice?:boolean
history:any
pastRouteInfo?: PastRouteinfo
}
interface IState {
isOpen: boolean
showToast: boolean
nameToRender: any
effectivePrice: any
offerPrice: any
}
class ProductCard extends React.Component<IProps, IState> {
state: IState = {
isOpen: false,
showToast: false,
nameToRender: '',
effectivePrice: 0,
offerPrice: 0
}
componentDidMount() {
const { product } = this.props
const { name } = product
const nameToRender = this.isLongName(name) ? name.slice(0,25) + '...' : name
const effectivePrice = product?.showPrice.price
const offerPrice = product && product.showPrice.offerPrice !== 0 &&
product.showPrice.offerPrice
this.setState({
nameToRender,
effectivePrice,
offerPrice
})
}
componentWillUnmount() {
// fix Warning: Can't perform a React state update on an unmounted component
this.setState = (state,callback)=>{
return;
};
}
onProductClick = () => {
const { onProductClick, product, history, pastRouteInfo = null } = this.props
if(pastRouteInfo !== null) {
this.setState({ isOpen: false })
const data = {
fromSuggested: true,
product,
pastRouteInfo,
}
const routeInfo = `/vendor/${product.providerId}/product/${product._id}`
history.push(routeInfo, data)
return
}
if (!onProductClick && pastRouteInfo === null) {
this.setState({
isOpen: true,
})
} else if(onProductClick) {
onProductClick(product)
}
}
dismissModal = () => {
setTimeout(() => {
this.setState({
isOpen: false,
})
}, 10)
}
setShowToast(showToast: boolean) {
this.setState({ showToast })
}
private renderModal() {
const { isOpen } = this.state
const { product, history } = this.props
const data = {
isOpen,
product
}
const routeInfo = `/vendor/${product.providerId}/product/${product._id}`
history.push(routeInfo, data)
}
validateIfExistLadders = (ladder:any) => ladder && ladder.length > 0
isLongName = (name: string) => name.length >= 25
render() {
const { product, hidePrice } = this.props
const { isOpen, nameToRender, effectivePrice, offerPrice } = this.state
const { filename, brand, ladder } = product
return (
<Fragment>
{isOpen && this.renderModal()}
<div className="product-card" onClick={() => this.onProductClick()}>
<div className="header" ></div>
<div className="body">
<div className="picture" style={{ backgroundImage: `url(${filename})` }}>
{ this.validateIfExistLadders(ladder) &&
<img className="img-ladder" src={small_ladder} alt="Escalonado"/>
}
</div>
<div className="tp">{brand}</div>
<div className="tp" >{nameToRender}</div>
</div>
{offerPrice && !hidePrice ? (
<div className="footer offer">
<Fragment>
<IonText className="tp-old">
<s>{asClp(effectivePrice)}</s>
</IonText>
<IonText className="tp-offer">{asClp(offerPrice)}</IonText>
</Fragment>
</div>
) : (
!hidePrice &&
<div className="footer">
<IonText>{asClp(effectivePrice)}</IonText>
</div>
)}
</div>
</Fragment>
)
}
}
export default track({page: 'ProductCard'})(ProductCard)

Delete an item without using index React

I want to delete the item which is right clicked. I am using onContextMenu and event.preventDefault to avoid showing the context menu. However, I don't know how to delete an item without the index. The requirement of this is not using index to do deletion.
The class App:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
activities: activities,
filteredActivities: activities,
isShow: false,
};
this.handleSearchChange = this.handleSearchChange.bind(this);
this.handleClickChange = this.handleClickChange.bind(this);
this.addItem = this.addItem.bind(this);
this.deleteItem = this.deleteItem.bind(this);
}
deleteItem(item) {
item.preventDefault();
const activities = this.state.activities.slice();
var response = confirm("Sure delete?");
if (response == true) {
// how to delete please?
this.setState({
activities: activities
});
}
}
render() {
const filteredActivities = this.props.filteredActivities;
const isShow = this.state.isShow;
return(
<div className="notificationsFrame">
<div className="panel">
<Header name={this.props.name} onClickSearch={this.handleClickChange} onClickAdd={this.addItem} />
{ isShow ? <SearchBar inputChanged={this.handleSearchChange} /> : null }
<Content activities={this.state.filteredActivities} onRightClick={this.deleteItem}/>
</div>
</div>
);
}
}
The structure of data:
const activities = [
{img_url: "assets/dog.jpg", time: "A hour ago", content: "Have lunch.", comment_count: "2" },
{img_url: "assets/dog.jpg", time: "5 hour ago", content: "Have breakfast.", comment_count: "0" },
{img_url: "assets/dog.jpg", time: "6 hour ago", content: "Get up.", comment_count: "1" }
];
The class content:
class Content extends React.Component {
render() {
return (
<div className="content">
<div className="line"></div>
{this.props.activities.map(activity =>
<ActivityItem img_url={activity.img_url} time={activity.time}
content={activity.content} comment_count={activity.comment_count} onContextMenu={this.props.onRightClick}/>
)}
</div>
);
}
}
The class item to show:
class ActivityItem extends React.Component{
render() {
return (
<div className="item" {...this.props}>
<div className="avatar">
<img src={this.props.img_url} />
</div>
<span className="time">
{this.props.time}
</span>
<p>
{this.props.content}
</p>
<div className="commentCount">
{this.props.comment_count}
</div>
</div>
);
}
}
You can do this way
deleteItem = (item) => {
const activities = this.state.activities.slice(item, 1);
var response = confirm("Sure delete?");
if (response == true) {
// how to delete please?
this.setState({
activities: activities
});
}
}
The class content:
class Content extends React.Component {
render() {
return (
<div className="content">
<div className="line"></div>
{this.props.activities.map(activity =>
<ActivityItem img_url={activity.img_url} time={activity.time}
content={activity.content} comment_count={activity.comment_count}
onContextMenu={() => this.props.onRightClick(activity)} />
)}
</div>
);
}
}
deleteItem need to modify a lot. The const activities = this.state.activities.slice(); is wrong and need to modify. I also need to find the item index. Then I just splice the item out.
deleteItem = item => {
event.preventDefault();
let activities = this.state.activities;
var response = confirm("Sure delete?");
if (response == true) {
for(var i = 0; i < activities.length; i++){
if(activities[i] == item){
activities.splice(i,1);
};
};
this.setState({
activities: activities
});
};
}
The class content:
class Content extends React.Component {
render() {
return (
<div className="content">
<div className="line"></div>
{this.props.activities.map(activity =>
<ActivityItem img_url={activity.img_url} time={activity.time}
content={activity.content} comment_count={activity.comment_count}
onContextMenu={() => this.props.onRightClick(activity)} />
)}
</div>
);
}
}
You can filter your activities array.
deleteItem(item) {
item.preventDefault();
const activities = this.state.activities;
var response = confirm("Sure delete?");
if (response == true) {
activities.filter((i) => {
if (i === item) {
items.splice(items.indexOf(i), 1);
}
});
this.setState({
activities: activities
});
}
}

Show and Hide specific component in React from a loop

I have a button for each div. And when I press on it, it has to show the div with the same key, and hide the others.
What is the best way to do it ? This is my code
class Main extends Component {
constructor(props) {
super(props);
this.state = {
messages: [
{ message: "message1", key: "1" },
{ message: "message2", key: "2" }
]
};
}
handleClick(message) {
//something to show the specific component and hide the others
}
render() {
let messageNodes = this.state.messages.map(message => {
return (
<Button key={message.key} onClick={e => this.handleClick(message)}>
{message.message}
</Button>
)
});
let messageNodes2 = this.state.messages.map(message => {
return <div key={message.key}>
<p>{message.message}</p>
</div>
});
return <div>
<div>{messageNodes}</div>
<div>{messageNodes2}</div>
</div>
}
}
import React from "react";
import { render } from "react-dom";
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
messages: [
{ message: "message1", id: "1" },
{ message: "message2", id: "2" }
],
openedMessage: false
};
}
handleClick(id) {
const currentmessage = this.state.messages.filter(item => item.id === id);
this.setState({ openedMessage: currentmessage });
}
render() {
let messageNodes = this.state.messages.map(message => {
return (
<button key={message.id} onClick={e => this.handleClick(message.id)}>
{message.message}
</button>
);
});
let messageNodes2 = this.state.messages.map(message => {
return (
<div key={message.key}>
<p>{message.message}</p>
</div>
);
});
const { openedMessage } = this.state;
console.log(openedMessage);
return (
<div>
{openedMessage ? (
<div>
{openedMessage.map(item => (
<div>
{" "}
{item.id} {item.message}{" "}
</div>
))}
</div>
) : (
<div> Not Opened</div>
)}
{!openedMessage && messageNodes}
</div>
);
}
}
render(<Main />, document.getElementById("root"));
The main concept here is this following line of code.
handleClick(id) {
const currentmessage = this.state.messages.filter(item => item.id === id);
this.setState({ openedMessage: currentmessage });
}`
When we map our messageNodes we pass down the messages id. When a message is clicked the id of that message is passed to the handleClick and we filter all the messages that do not contain the id of the clicked message. Then if there is an openedMessage in state we render the message, but at the same time we stop rendering the message nodes, with this logic {!openedMessage && messageNodes}
Something like this. You should keep in state only message key of visible component and in render method you should render only visible component based on the key preserved in state. Since you have array of message objects in state, use it to render only button that matches the key.
class Main extends Component {
constructor(props) {
super(props);
this.state = {
//My array messages: [],
visibleComponentKey: '',
showAll: true
};
handleClick(message) {
//something to show the specific component and hide the others
// preserve in state visible component
this.setState({visibleComponentKey : message.key, showAll: false});
};
render() {
const {visibleComponentKey, showAll} = this.state;
return (
<div>
{!! visibleComponentKey && ! showAll &&
this.state.messages.filter(message => {
return message.key == visibleComponentKey ? <Button onClick={e => this.handleClick(message)}>{message.message}</Button>
) : <div /> })
}
{ !! showAll &&
this.state.messages.map(message => <Button key={message.key} onClick={e => this.handleClick(message)}>{message.message}</Button>)
}
</div>
);
}
}
I haven't tried it but it gives you a basic idea.
I cannot reply to #Omar directly but let me tell you, this is the best code explanation for what i was looking for! Thank you!
Also, to close, I added a handleClose function that set the state back to false. Worked like a charm!
onCloseItem =(event) => {
event.preventDefault();
this.setState({
openedItem: false
});
}

Could not check the input value properly using React.js

I need to check the user input value using React.js but its not working as expected. I am explaining my code below.
import React, { Component } from "react";
import TodoItems from "./TodoItems";
import "./TodoList.css";
class TodoList extends Component {
constructor(props, context){
super(props, context);
this.state={
items:[]
}
this.listOfKeyWords = ["script", "embed", "object", "iframe"];
this.addItem=this.addItem.bind(this);
this.deleteItem = this.deleteItem.bind(this);
this.editItem = this.editItem.bind(this);
}
addItem(e){
e.preventDefault();
if(this.state.editKey){
this.saveEditedText();
return;
}
let maliciousStr = this.listOfKeyWords.find(tag => {
return this.inputElement.value.toLowerCase().indexOf(tag) !== -1;
});
if (!maliciousStr) {
var itemArray = this.state.items;
if (this.inputElement.value !== '') {
itemArray.unshift({
text:this.inputElement.value,
key:Date.now()
})
this.setState({
items:itemArray
})
this.divRef.insertAdjacentHTML("beforeend", '<p className="textcolor">'+this.inputElement.value+' has added successfully</p>');
this.inputElement.value='';
setTimeout( () => {
this.divRef.querySelector(':last-child').remove();
}, 3000);
}
}else{
this.inputElement.value='';
}
}
deleteItem(key) {
const result = window.confirm('Are you sure to delete this item');
if (result) {
var filteredItems = this.state.items.filter(function (item) {
return (item.key !== key);
});
this.setState({
items: filteredItems
});
}
}
editItem(key){
this.state.items.map(item =>{
if (item.key==key) {
this.inputElement.value=item.text;
}
})
this.setState({editKey: key});
}
saveEditedText(){
let value = this.inputElement.value;
this.setState(prevState => ({
items: prevState.items.map(el => {
if(el.key == prevState.editKey)
return Object.assign({}, el, {text: value});
return el;
}),
editKey: ''
}));
let maliciousStr = this.listOfKeyWords.find(tag => {
return this.inputElement.value.toLowerCase().indexOf(tag) !== -1;
});
if (!maliciousStr) {
this.divRef.insertAdjacentHTML("beforeend", '<p className="textcolor">'+this.inputElement.value+' has updated successfully</p>');
setTimeout( () => {
this.divRef.querySelector(':last-child').remove();
}, 3000);
this.inputElement.value='';
}else{
this.inputElement.value='';
}
}
render() {
return (
<div className="todoListMain">
<div className="header" id="parentDiv">
<div className="pageHeading" dangerouslySetInnerHTML={{ __html: "Todo Demo Application" }}></div>
<div className="wrapper">
<div ref={divEl => {
this.divRef = divEl;
}}></div>
<form onSubmit={this.addItem}>
<input ref={(a)=>this.inputElement=a} placeholder="enter task">
</input>
<button type="submit">{this.state.editKey? "Update": "Add"}</button>
</form>
<TodoItems entries={this.state.items} delete={this.deleteItem} edit={this.editItem}/>
</div>
</div>
</div>
);
}
}
export default TodoList;
Here I am adding data and after submitting the message is displaying on the top. Here I have one validation if user input has value like <script>Hii</script> the the message will not display or the value can not be added but in my coding its not happening like this. I need if <script>Hii</script> is there those will be filtered out.

Categories

Resources