How do I remove an object from an array dynamically after clicking the "remove" button.
For example, in the below code the table has n rows. After clicking on a particular remove button, it should delete only that row. Just like a todo list.
But in my case entire table is getting deleted.
const [items,itemList]=useState([]);
const [companyName,setCompanyName]=useState('');
const [experience, setExperience]=useState();
//Adding Items to Array after clicking "Add" button
const handleClose=()=>{
itemList((old)=>{
return [...old,{companyName,experience}]
})
}
//Removing Items from Array After clicking Remove button
const removeItem=(index)=>{
itemList((old)=>{
return old.filter((arrEle,i)=>{
return (
i!==index
);
})
})
}
//Displaying Array of objects on UI
<Table striped bordered hover size="sm">
<thead>
<tr>
<th>Company Name</th>
<th>Experience</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{
items.map((item,index)=>{
return(
<tr>
<td>{item.companyName}</td>
<td>{item.experience}</td>
<td><button onClick={()=>{removeItem(index)}}>Remove</button></td>
</tr>
)
})
}
</tbody>
</Table>
Issue
The main issue in your code is that you've a bunch of buttons being rendered inside a form and nearly all of them don't specify a button type. The default for buttons is type="submit", so when any of them are clicked they are submitting the form and the form is taking the default submit action which also happens to reload the page. When the page reloads your app reloads and loses the local component state.
Button type attribute
The default behavior of the button. Possible values are:
submit: The button submits the form data to the server. This is the default if the attribute is not specified for buttons associated with a <form>, or if the attribute is an empty or invalid value.
reset: The button resets all the controls to their initial values, like <input type="reset">. (This behavior tends to annoy users.)
button: The button has no default behavior, and does nothing when pressed by default. It can have client-side scripts listen to the element's events, which are triggered when the events occur.
Solution
Explicitly specify the button types.
The delete button
<button
type="button" // <-- "button" type
onClick={() => {
removeItem(item);
}}
>
Remove
</button>
The form buttons to submit, reset, and open the modal
<Button variant="primary" type="submit"> <-- "submit" type
Submit
</Button>
<Button
variant="primary"
type="reset" // <-- "reset" type
style={{ margin: '0 20px' }}
>
Reset Form
</Button>
<Button
variant="primary"
type="button" // <-- "button" type
onClick={handleShow}
style={{ margin: '0 15px' }}
>
Add Experience
</Button>
Note: The submit button is still going to submit the form and since you've not any onSubmit callback on the Form component this button, as-is, will cause the page to reload. You will want to add a submit handler and call preventDefault on the onSubmit event object.
const submitHandler = e => {
e.preventDefault();
// handle form data or whatever
};
...
<Form onSubmit={submitHandler}>
...
Generally we should not remove any item from array based on it's index. So try to remove items based on unique values:-
const removeItem = item => {
itemList(oldList => {
return oldList.filter((arrEle, i) => arrEle.companyName !== item.companyName);
});
};
And pass item in removeItem function like below:-
onClick={() => removeItem(item)}
Add key to tr like below:-
<tr key={`${item.companyName}-${index}`}>
Working demo: https://stackblitz.com/edit/react-cxkbge
Related
<section key={i}>
<input
type='radio'
key={attribute.name + item.id}
id={attribute.name + item.id}
name={attribute.name}
value={item.value}
defaultChecked={i === 0}
onClick={(event) => inputClick(attribute, event)}
/>
<label
htmlFor={attribute.name + item.id}
>
{
item.value
}
</label>
</section>
The code above is more or less what a .map() function is supposed to return in my React.js APP, creating input radio buttons for customizing a product before adding it to the cart state. All I did was remove some classes, etc. that were there for CSS purposes, etc.
Ideally, what is supposed to happen here is that...
When rendering the product page for the first time, the very first input radio button that is returned by .map() should be checked by default. This works thanks to the "defaultChecked" attribute. No problems here.
After I click a different input radio button (not the "defaultChecked" one), the checked input should change and all the other inputs should be left unchecked. As I understand it, input radio buttons with the same 'name' attribute do this automatically.
When clicking the input radio button, the "onClick" function should trigger. This is where the code is not functioning as I wish it would.
The issue is that when my page renders for the first time and I click on an input radio button (other than the "defaultChecked") the "onClick" function does not trigger. It only triggers when I have clicked on a different input once and then on another.
A.k.a. it triggers on the second click. After that second click, it works as intended - every time a different input radio button is selected/clicked on - the function triggers, but not for the very first time.
I tested this with console.log("I am triggered") at the end of the "onClick" "inputClick(attribute, event)" function, and only on the second click would the console log "I am triggered".
I was able to fix the issue by removing the "defaultChecked" attribute. I think the issue might be tied to the fact that the "onClick" function is only able to be triggered when one input gains the "checked" attribute and another loses it, but the "defaultChecked" attribute does not count as an input being "fully checked" or something like that.
I could leave it at that, but the project that I am working on required me to have a default checked input radio button on the first-page render. So, I can't just delete the "defaultChecked" attribute and call it a day.
Any ideas on what could be causing this behavior?
UPDATE1
The following is the body of the inputclick() function:
//* Handle input selection
const inputClick = (attribute, event) => {
//* Parse state
let state = JSON.parse(JSON.stringify(itemState));
//* Get all the required values from the clicked input
const attributeId = attribute.id;
const itemTargetValue = event.target.value;
//* Check if the attribute is in state
const attributeIs = state.some(item => item.id === attributeId);
//* If the attribute does not exsist - add the attribute to state
if(attributeIs === false) {
const obj = {
id: attributeId,
selectedItem: itemTargetValue
};
state.push(obj);
return setitemState(state);
}
//* If the attribute id already exsists in state
if(attributeIs) {
//* Find the index of the attribute in question
const attributeIndex = state.map(object => object.id).indexOf(attributeId);
const attributeInQuestion = state[attributeIndex].selectedItem;
//* If the attribute's item's id is the same as the seelected input - do nothing
if(attributeInQuestion === itemTargetValue) {
return
}
//* If the attribute's item's id is not the same - change it to the new value
if(attributeInQuestion !== itemTargetValue) {
state[attributeIndex].selectedItem = itemTargetValue;
console.log(state);
return setitemState(state);
}
}
};
Here is the working code that fixes the issue.
Yes, there is some streamlining, for example, code shortening, etc. Yet, the difference that solves the issue is that...
The code that I had posted in the question originally was working. Meaning, that it was firing the inputClick() function and changing which input was selected, the problem was that...
...the defaultChecked logic in the was preventing the chosen input from being rendered as a selected input a.k.a. to change its CSS styling.
Bellow is the new onClick() function.
//* Handle input selection
const inputClick = (product, attribute, event) => {
let newCart = JSON.parse(JSON.stringify(cartState));
const productId = product.id;
const attributeId = attribute.id;
const itemTargetValue = event.target.value;
//* Find the product in cart state */
const productIndex = newCart.map((object) => object.id).indexOf(productId);
//* Find the attribute by id in question */
const attributeIndex = newCart[productIndex].selectedAttributes.map(object => object.id).indexOf(attributeId);
//* Change the products selected attribute item */
newCart[productIndex].selectedAttributes[attributeIndex].selectedItem = itemTargetValue;
setcartState(newCart);
};
Below is what the "inside" of the looks like now.
<input
type='radio'
key={product.id + attribute.name + item.id}
id={product.id + attribute.name + item.id}
name={product.id + attribute.name}
value={item.value}
defaultChecked={product.selectedAttributes[selectedId].selectedItem === item.value}
onChange={(event) => inputClick(product, attribute, event)}
>
</input>
I am a beginner in React.js and I'm trying to implement a delete functionality for an app. On clicking the button, the form gets submitted and the function runs, however it shows an error in the console which is there for a split second so I couldn't read it. I have tried setTimeout to atleast see the error but it has no effect. The error just appears for a fraction of a second in the console and then disappears. The code is:
const Note = ({object}) => {
const {title, desc} = object;
const handleDel = (e, object)=>{
e.preventDefault();
console.log(object)
}
return (
<>
<div className="single-note">
<div>
<h1>{title}</h1>
<p>{desc}</p>
</div>
<form onSubmit={()=>handleDel(object)}>
<button type="submit" className="submit">
<FaRegTrashAlt/>
</button>
</form>
</div>
</>
)
}
export default Note
I guess you missed to pass the event and it is trying to invoke preventDefault on the object param that is passed. Try the below code:
onSubmt={(e) => handleDel(e,object)}
I am learning React. I have an array in my state which I am mapping through to render each value of the array on its own button. When the user clicks one of those buttons, I want to call a function to show some info specific to that button (the button represents a specific customer order). I tried to set state in the onClick event, but I got an 'Maximum update depth exceeded' error. If there is a way to set state from the onClick, that would solve my problem as I could just set the value of item of the particular button clicked to state and use it later. My other thought is to send the item to the showInfo function...however, I don't even know if that is possible. How can I call the same function from this list of buttons, and make the outcome unique to the button?
showInfo = (e) => {
}
componentDidMount() {
this.showAllOrders();
}
render() {
return (
<div>
<h1>Hello from past orders!!!</h1>
<ul id="orderList">
{this.state.orderDates.map((item =>
<li><button onClick={this.showInfo} key={item}>{item}</button></li>
))}
</ul>
</div>
)
}
}
You could provide the function showInfo with info about the button via params.
So that it would be onClick={() => this.showInfo(item)}
Use an inline arrow function as a callback to your onClick and pass the data you want.
...
showInfo = (item) => {
console.log(item);
}
...
render() {
return (
<div>
<h1>Hello from past orders!!!</h1>
<ul id="orderList">
{this.state.orderDates.map((item =>
<li><button onClick={() => this.showInfo(item)} key={item}>{item}</button></li>
))}
</ul>
</div>
)
Also, never setState in your render method and thats why you got max depth error in your earlier try.
Ive got some standard mapping going on.
{MEMBERSHIPS.map((mItem, index) => (
<TableCell
className="text-uppercase text-center"
colSpan={2}
padding="dense"
value={mItem.label}
key={mItem.key}
>
<Button onClick={this.handleClickOpen}>{mItem.label}</Button>
<Dialog
disableBackdropClick
disableEscapeKeyDown
open={this.state.open}
onClose={this.handleClose}
>
<DialogTitle>
Choose bulk edit {mItem.label} status
</DialogTitle>
...
The value of {mItem.label} is correctly pulling through the headers, but if I use that key again within the mapping staetment it brings back the last item in the array... I would expect {mItem.label} to be the same wherever its used.
https://codesandbox.io/s/kxrk5mnqjr
If you go to the above codesandbox... click on a heading of either seniors, Juniors or Infants - this is a button
<Button onClick={this.handleClickOpen}>{mItem.label}</Button>
It opens up a dialog where I want to use the heading value again {mItem.label} but the result is different from the header display. e.g. If I clicked the Seniors button I would expect the Seniors dialog text however it comes back with "infants" in all instances.
The main problem is that you are using the same state value to open/close all the dialogs this.state.open. So when you click on one button, all 3 dialogs are opened, and you see the last one which is on top.
To fix this :
handleClickOpen = value => {
this.setState({ [`open${value}`]: true });
};
handleClose = value => {
this.setState({ [`open${value}`]: false });
};
And
<Button
onClick={this.handleClickOpen.bind(this, mItem.value)}
>
{mItem.label}
</Button>
<Dialog
disableBackdropClick
disableEscapeKeyDown
open={this.state[`open${mItem.value}`]}
onClose={this.handleClose.bind(this, mItem.value)}
>
...
Complete code https://codesandbox.io/s/pm0ovrvl97
I have this function
handleChangeButton = (e) => {
alert(e.target.value)
this.props.setFieldValue('degreeLevel', e.target.value);
}
and in my component render, I have
<div className="twelve columns">
<p>Degree Level</p>
<Button
variant="raised"
label="Default"
onClick = { this.handleChangeButton }
value="Doctorate"
>
Doctorate
</Button>
<Button variant="raised" label="Default">
Masters
</Button>
<Button variant="raised" label="Default">
Undergraduate
</Button>
</div>
So, what I want to do is, when I click the Doctorate button, it should this.props.setFieldValue to degreeLevel which is one of the fields in my Formik form. When I click the button, the alert gives me undefined which means it's not reading the value Doctorate.
How can I make e.target.value read the value of the button?
Use currentTarget instead of target
handleChangeButton = (e) => {
alert(e.currentTarget.value)
this.props.setFieldValue('degreeLevel', e.currentTarget.value);
}
#yugantarkumar #monsto, This is not something that is specific to Material UI, rather this is how it is done in JS by using event bubbling. You can read more about the difference here: http://www.qc4blog.com/?p=650
currentTarget refers to the element, to which the event listener is attached to, while target indicates the element that was interacted with.
In MUI this issue is caused by clicking on MuiButton-label component instead of the MuiButtonBase component.