I have Built a table in which the user does some calculations, but when any row gets removed the values entered appear in the row below it and the next row's value below it, and so on.
What I want is a the user removes any row it should get removed completely with the values entered and the next row should take its place with its own values.
Image 1:Here I have entered values in the First Two rows you can see in the image.
[1]: https://i.stack.imgur.com/wWUBE.png
Image 2: I deleted the first row but as you can see the value of that is still there.
[2]: https://i.stack.imgur.com/HuOuA.png
App.js
const [NewRow2, setNewRow2] = useState(data);
const [IntensificationRatio, setIntensificationRatio] = useState(0)
const [editFormData, setEditFormData] = useState({
Injection_Speed: "",
Fill_Time: "",
Peak_Inj_Press: "",
Viscosity: "",
Shear_Rate: ""
})
const [isRowId, setIsRowId] = useState(null)
const handleEditFormChange = (event) => {
event.preventDefault();
const fieldName = event.target.getAttribute("name");
const fieldValue = event.target.value;
const newFormData = { ...editFormData };
newFormData[fieldName] = fieldValue;
setEditFormData(newFormData);
}
const handleEditFormSubmit = (event) => {
event.preventDefault();
const editedValue = {
id: isRowId,
Injection_Speed: editFormData.Injection_Speed,
Fill_Time: editFormData.Fill_Time,
Peak_Inj_Press: editFormData.Peak_Inj_Press,
Viscosity: editFormData.Fill_Time * editFormData.Peak_Inj_Press * IntensificationRatio,
Shear_Rate: 1 / editFormData.Fill_Time,
}
const newValues = [...NewRow2];
const index = NewRow2.findIndex((value) => value.id === isRowId)
newValues[index] = editedValue;
setNewRow2(newValues);
}
const deleteRow2 = (id) => {
const updatedRows = [...NewRow2].filter((rowId) => {
return rowId !== id;
});
setNewRow2(updatedRows);
};
HandlEditchange and HandleEditSubmit are the two functions that deal with data entered in the row and deleteRow2 for removing the row.
Table Row's
<tr onClick={() => setId(NewRow.id)}>
<td> {rowId} </td>
<td> <input type='text' className="form-control" name="Injection_Speed" onChange={handleEditFormChange}/> </td>
<td> <input type='text' className="form-control" name="Fill_Time" onChange={handleEditFormChange}/> </td>
<td> <input type='text' className="form-control" name="Peak_Inj_Press" onChange={handleEditFormChange}/> </td>
<td> <i className="fa fa-trash viscocity_icons" onClick={() => deleteRow2(NewRow.id)}></i> </td>
</tr>
CodeSandBox Link: Rest You Can Check Over Here.
https://codesandbox.io/s/focused-gauss-lql7r?file=/src/Edit.js
There were a few issues as I identified.
handleEditFormChange was not updating NewRow2 state. You can fix it like below. Need to pass row id along with the event.
const handleEditFormChange = (event, id) => {
event.preventDefault();
setNewRow2((prevState) => {
return prevState.map((item) =>
item.id === id
? { ...item, [event.target.name]: event.target.value }
: item
);
});
};
Need to change the onChange handlers like below.
onChange={(e) => handleEditFormChange(e, NewRow.id)}
Need to bind values from the NewRow2 to each input.
value={NewRow.Injection_Speed}
same for Fill_Time, Peak_Inj_Press
deleteRow2 should be using id correctly when filtering.
const deleteRow2 = (id) => {
const updatedRows = NewRow2.filter((item) => {
return item.id !== id;
});
setNewRow2(updatedRows);
};
Pass the id of the row to be deleted in the onClick handler.
onClick={() => deleteRow2(NewRow.id)}
Code sandbox => https://codesandbox.io/s/stoic-hopper-zi8mw?file=/src/Edit.js:2738-2775
Related
So Im having problems with a form in which takes in text inputs to be set on an object. This object will then be updated in a hashmap of <key, object>.
So far, I can type in the input areas, but if I add another item in the hashmap which generates another div with the input elements it will contain the same value of the previous inputs.
So I need a way to reset those labels within the form and a way to update the hashmap.
I got an updateMap function, but I don't know where to place it for it to await the changes.
Edit: I need to reset the state of link, but when I do a function for it. It says something about preventing infinite loops.
export default function Admin() {
const [link, setLink] = useState({ name: "", link: "" });
const [links, setLinks] = useState(new Map());
const clickAddLink = () => addToMap(links.size + 1, link);
const deleteOnMap = (key) => {
setLinks((prev) => {
const newState = new Map(prev);
newState.delete(key);
return newState;
});
};
const getName = (key) =>
{
let linkFromKey = links.get(key)
return linkFromKey.name
}
const getLink = (key) =>
{
let linkFromKey = links.get(key)
return linkFromKey.link
}
const addToMap = (key, value) => {
setLinks((prev) => new Map([...prev, [key, value]]));
};
const updateMap = (key, value) => {
setLinks((prev) => new Map([...prev, [key, value]]));
};
const clear = () => {
setLinks((prev) => new Map(prev.clear()));
};
.... skipping code ....
<div>
{console.log(link.name)}
{console.log(link.link)}
{[...links.keys()].map((key) => (
<div key={key}>
{links.size > 0 && (
<div>
<form>
<span>Name</span>
<input
type="text"
placeholder={getName(key)}
required
value={link.name}
onChange={(e) =>
setLink({ name: e.target.value })
}
/>
<span>Link</span>
<input
type="text"
placeholder={getLink(key)}
required
value={link.link}
onChange={(e) =>
setLink({ link: e.target.value })
}
/>
</form>
</div>
)}
</div>
))}
</div>
{console.log(link.name)}
{console.log(link.link)}
</div>
```
i am using table input field to update state under map function to render it according to number of elements in the state.But when I used value={item.account} values are not updated in the state.which works fine when I use **value={accountCounter.account} where accountCounter is reactjs hook of type
const[accountCounter,setAccountCounter]=useState([
{ id: 1, account:"" ,accountOwner:""},
{ id: 2, account: "hi",accountOwner:"" },
{ id: 3, account: "bu" ,accountOwner:""}
And here is my rendering function
accountCounter.map((item,key)=>{
return(
<tr key={key}>
<td><input type="text" value={item.account}
name="account" onChange={(e)=>handleAccountCounter(e,item)}/></td>
<td><input type="text" value={item.accountOwner}
name="accountName" onChange={(e)=>handleAccountCounter(e,item)}/></td>
<td><span onClick={()=>deleteAccount(item.id)}>X</span></td>
</tr>
)
})}
here is my handleAccountCounter
const handleAccountCounter=(event,counter)=>{
const index = accountCounter.indexOf(counter);
accountCounter[index][event.target.name]=event.target.value;
setAccountCounter(accountCounter)
}
But the state is not getting modified when in input field value={item.account}.dont know why..will you help me out
Use the previous state values to create a new array:
const App = () => {
const [accountCounter, setAccountCounter] = useState([
{ id: 1, account: "", accountOwner: "" },
{ id: 2, account: "hi", accountOwner: "" },
{ id: 3, account: "bu", accountOwner: "" }
]);
const handleAccountCounter = (event, counter) => {
setAccountCounter((prevAccountCounter) => {
const newCounter = [...prevAccountCounter];
newCounter[prevAccountCounter.indexOf(counter)][event.target.name] =
event.target.value;
return newCounter;
});
};
const deleteAccount = (id) => {
setAccountCounter((prevAccountCount) =>
prevAccountCount.filter((item) => item.id !== id)
);
};
return accountCounter.map((item, index) => (
<tr key={index}>
<td>
<input
type="text"
value={item.account}
name="account"
onChange={(e) => handleAccountCounter(e, item)}
/>
</td>
<td>
<input
type="text"
value={item.accountOwner}
name="accountOwner"
onChange={(e) => handleAccountCounter(e, item)}
/>
</td>
<td>
<span onClick={() => deleteAccount(item.id)}>X</span>
</td>
</tr>
));
};
Instead of this
const handleAccountCounter = (event,counter) => {
const index = accountCounter.indexOf(counter);
accountCounter[index][event.target.name]=event.target.value;
setAccountCounter(accountCounter)
}
Do like this
const handleAccountCounter = (event, counter) => {
let temp = [...accountCounter] // Make a copy of state and then perform operations
const index = temp.indexOf(counter);
temp[index][event.target.name] = event.target.value;
setAccountCounter(temp)
}
Using Kartikey's answer, but you should use a callback because state updates are asynchronous:
const handleAccountCounter = (event, counter) => {
setAccountCounter((prev) => {
let newCounter = [...prev];
newCounter[prev.indexOf(counter)][event.target.name] = event.target.value;
return newCounter;
});
};
This ensures that the state updates in the correct order. See here for more info:
https://dmitripavlutin.com/how-react-updates-state/
I have simple fields. I want to populate the input boxes based on the data that I get from axios response. See below image
I am using hooks, to save data to state.
My question is how I am able to populate input boxes upon clicking get details button (response from api).
see below codes
const [data, setData] = useState([])
const [advisoryDetails, setadvisoryDetails] = useState({
ADVISORYID: '',
ADVISORYDESC: '',
CREATEDBY:'',
MODIFIEDBY:'',
STATUS1: ''
})
const [advisoryDetails1, setadvisoryDetails1] = useState([])
const [advisoryID, setadvisoryID] = useState('')
const getAdvisoryTest = async () => {
await axios.post('/API',advisoryID)
.then(response => {
console.log(response.data)
setData(response.data)
console.log('data',data)
setadvisoryDetails1(response.data)
console.log('advisoryDetails1',advisoryDetails1)
alert('success')
})
advisoryDetails1.map(adv => {
advisoryDetails.ADVISORYID = adv.ADVISORYID;
advisoryDetails.ADVISORYDESC = adv.ADVISORYDESC;
advisoryDetails.CREATEDBY = adv.CREATEDBY;
advisoryDetails.MODIFIEDBY = adv.MODIFIEDBY;
if(adv.CREATEDBY && adv.MODIFIEDBY != '')
{
advisoryDetails.STATUS1 = 'Modified'
}
else{ advisoryDetails.STATUS1 = 'New'}
console.log('populate',advisoryDetails)
})
}
const txtAdvIdOnChange = e =>{
setadvisoryID(prevState =>({
...prevState,
'ADVISORYID':e.target.value
}));
console.log('onChange ID:',advisoryID)
}
return(
<div>
<label>AdvisoryID: </label>
<input type='text' placeholder='Advisory ID' className='txtAdvisoryID' onChange={txtAdvIdOnChange} />
<button onClick={()=>getAdvisoryTest()}>Get Details</button>
<br /><br />
<label>Advisory Desc: </label>
<input type='text' placeholder='textbox1' className='txtAdvisoryDesc' value={advisoryDetails&&advisoryDetails.ADVISORYDESC} disabled/>
<br/>
<label>New / Modified: </label>
<input type='text' placeholder='textbox2' className='txtStatus' value={advisoryDetails&&advisoryDetails.STATUS1} disabled/>
</div>)
On those codes input boxes was not populated, even in console.log
Hope you can help me thank you.
I think you can refactor your code like below.
I've moved set setadvisoryDetails to within .then() of axios because you're using the same data and you're don't have to go through the loop if you just want the last iteration's value. And in the inputs you don't have to check if advisoryDetails exists or has non empty value because you've initialized it in const [advisoryDetails, setadvisoryDetails] = useState({...})
const App = (props) => {
const [data, setData] = useState([])
const [advisoryDetails, setadvisoryDetails] = useState({
ADVISORYID: '',
ADVISORYDESC: '',
CREATEDBY: '',
MODIFIEDBY: '',
STATUS1: ''
})
const [advisoryDetails1, setadvisoryDetails1] = useState([])
const [advisoryID, setadvisoryID] = useState('')
const getAdvisoryTest = () => {
axios.post('/API', advisoryID)
.then(response => {
const respData = response.data;
setData(respData)
setadvisoryDetails1(respData)
console.log({
respData, data, advisoryDetails1
});
alert('success');
if (respData.length > 0) {
const adv = respData[respData.length - 1];
setadvisoryDetails((prevState) => ({
...prevState,
...adv,
STATUS1: adv.CREATEDBY && adv.MODIFIEDBY != '' ? 'Modified' : 'New'
}))
}
})
}
const txtAdvIdOnChange = e => {
setadvisoryID(prevState => ({
...prevState,
'ADVISORYID': e.target.value
}));
console.log('onChange ID:', advisoryID)
}
return (
<div>
<label>AdvisoryID: </label>
<input type='text' placeholder='Advisory ID' className='txtAdvisoryID' onChange={txtAdvIdOnChange} />
{/* If you're just passing a function without any param or event params, then just pass the function name like a variable */}
<button onClick={getAdvisoryTest}>Get Details</button>
<br /><br />
<label>Advisory Desc: </label>
<input type='text' placeholder='textbox1' className='txtAdvisoryDesc' value={advisoryDetails.ADVISORYDESC} disabled />
<br />
<label>New / Modified: </label>
<input type='text' placeholder='textbox2' className='txtStatus' value={advisoryDetails.STATUS1} disabled />
</div>
);
}
when you click the get details button your state was not updating that was the issue(value only change when the state was updated otherwise it is not updated)
//before don't do like this
advisoryDetails1.map(adv => {
advisoryDetails.ADVISORYID = adv.ADVISORYID;
advisoryDetails.ADVISORYDESC = adv.ADVISORYDESC;
advisoryDetails.CREATEDBY = adv.CREATEDBY;
advisoryDetails.MODIFIEDBY = adv.MODIFIEDBY;
if(adv.CREATEDBY && adv.MODIFIEDBY != '')
{
advisoryDetails.STATUS1 = 'Modified'
}
else{ advisoryDetails.STATUS1 = 'New'}
console.log('populate',advisoryDetails)
})
//after(only last element of advisoryDetails1 array was updated in the state)
advisoryDetails1.forEach(adv => {
let STATUS1 ='New'
if(adv.CREATEDBY && adv.MODIFIEDBY != '')
{
STATUS1 = 'Modified'
}
setadvisoryDetails({ADVISORYID : adv.ADVISORYID,
ADVISORYDESC:adv.ADVISORYDESC,
CREATEDBY:adv.CREATEDBY,
MODIFIEDBY:adv.MODIFIEDBY,
STATUS1:STATUS1
})
})
if you want to view your last element in your advisoryDetails1 array do like this
let adv=advisoryDetails1[advisoryDetails1.length -1];
let STATUS1 ='New'
if(adv.CREATEDBY && adv.MODIFIEDBY != '')
{
STATUS1 = 'Modified'
}
setadvisoryDetails({ADVISORYID : adv.ADVISORYID,
ADVISORYDESC:adv.ADVISORYDESC,
CREATEDBY:adv.CREATEDBY,
MODIFIEDBY:adv.MODIFIEDBY,
STATUS1:STATUS1
})
//it update your advisoryDetails state when advisoryDetails1 changed
useEffect(()=>{
advisoryDetails1.forEach(adv => {
let STATUS1 ='New'
if(adv.CREATEDBY && adv.MODIFIEDBY != '')
{
STATUS1 = 'Modified'
}
setadvisoryDetails({ADVISORYID : adv.ADVISORYID,
ADVISORYDESC:adv.ADVISORYDESC,
CREATEDBY:adv.CREATEDBY,
MODIFIEDBY:adv.MODIFIEDBY,
STATUS1:STATUS1
})
})
},[advisoryDetails1]);
//check whether advisoryDetails is changed or not
useEffect(()=>{
console.log('populate',advisoryDetails)
},[advisoryDetails])
everything fine but doesn't do like this only the last element of the array was updated so you must need some changes based on your requirements
On your input value properties try adding a space either side of the &&.
<input type='text' placeholder='textbox2' className='txtStatus' value={advisoryDetails.STATUS1 && advisoryDetails.STATUS1} disabled/>
I have a form with one initial empty input field that I want to clone using a Add button and to remove with a Remove one.
As it's not recommended to use index for the keys with dynamic forms, I tried to use uniqid module. But each time the state is updating, keys are renewed and I don't have unique data to identify each input of the form. I can add some items, but can't remove.
input fields have no unique values, no id, how can I do ?
const Form = () => {
const update = e => {};
const items = [{ content: "", color: "" }];
return (
<Fragment>
{items.map((item, idx) => (
<input
htmlFor={`item_${idx}`}
value={item.content}
onChange={update("item", idx)}
/>
))}
<button onClick={e => dispatch(add(idx))}>Add</button>
<button onClick={e => dispatch(remove(idx))}>Remove</button>
</Fragment>
);
You may simply extend your existing items to have unique id property - at its very simplest, you may assign the value of maximum used id increased by 1 to that property - I guess, it'll do the trick for most of practical use cases:
const [inputs, setInputs] = useState([{id:0,value:''}]),
onRowAdd = () => {
const maxId = Math.max(...inputs.map(({id}) => id))
setInputs([...inputs, {id:maxId+1, value:''}])
}
With that, you'll have unique id to anchor to as you delete rows:
onRowRemove = idToDelete => setInputs(inputs.filter(({id}) => id != idToDelete))
Following is the demo of this concept:
const { useState } = React,
{ render } = ReactDOM
const Form = () => {
const [inputs, setInputs] = useState([{id:0,value:''}]),
onInput = (id,value) => {
const inputsCopy = [...inputs],
itemToModify = inputsCopy.find(item => item.id == id)
itemToModify.value = value
setInputs(inputsCopy)
},
onRowAdd = () => {
const maxId = Math.max(...inputs.map(({id}) => id))
setInputs([...inputs, {id:maxId+1, value:''}])
},
onRowRemove = idToDelete => setInputs(inputs.filter(({id}) => id != idToDelete)),
onFormSubmit = e => (e.preventDefault(), console.log(inputs))
return (
<form onSubmit={onFormSubmit} >
{
inputs.map(({id,value}) => (
<div key={id}>
<input
onKeyUp={({target:{value}}) => onInput(id,value)}
/>
<input type="button" onClick={onRowAdd} value="Add" />
<input type="button" onClick={() => onRowRemove(id)} value="Remove" />
</div>
))
}
<input type="submit" value="Log Form Data" />
</form>
)
}
render (
<Form />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
You should create a variable that starts from 0 and adds 1 every time you add a button. That way you will keep track of everyone. Here's an example
let i = 0
const add () => {
//your function to add
i++
//remember i will be the id now
}
I have following code where i am adding dynamic row and column. I want to delete row which on click.
But, getting difficulty figure it out.
class TableCells extends Component {
onDeleteEvent = event => {
// Delete row
};
render() {
var numberOfRow = this.props.numberOfRow; // user input
var numberOfColumn = this.props.numberOfColumn; // user input
var rows = Array.from({length: numberOfRow}).map((_, rowIdx) => (
<tr key={rowIdx}>{
Array.from({length: numberOfColumn}).map((_, colIdx) => (
<EditableCell key={colIdx}/>
))
}
<td>
<input type="button" onClick={this.onDeleteEvent} value="X" />
</td>
</tr>
))
return (<tbody>{rows}</tbody>);
}
}
Any help would be greatly appreciated.
Check this sandbox.
A little simplified, but works for you.
https://codesandbox.io/s/9l9wnonyp
Basically, move your object in the state, and when you delete the item send his ID as parameter, and just set the new state, and it will rerender.
<input
type="button"
onClick={() => this.onDeleteEvent(rowIdx)}
value="X"
/>
And onDeleteFunction:
onDeleteEvent = id => {
// Delete row
let rows = this.state.rows;
rows.splice(id, 1);
this.setState({
rows
});
};
For any other question just comment here, and I will help you :)
The right way to do this would be to:
class TableCells extends Component {
render() {
const onDeleteEvent = (id) => () => this.props.onDeleteEvent(id);
var numberOfRow = this.props.numberOfRow; // user input
var numberOfColumn = this.props.numberOfColumn; // user input
var rows = Array.from({length: numberOfRow}).map((_, rowIdx) => (
<tr key={rowIdx}>{
Array.from({length: numberOfColumn}).map((_, colIdx) => (
<EditableCell key={colIdx}/>
))
}
<td>
<input type="button" onClick={onDeleteEvent(colIdx)} value="X" />
</td>
</tr>
))
return (<tbody>{rows}</tbody>);
}
}
And wherever you use TableCellsand presumably store the number of rows you are passing as a prop to TableCells, you would have a function that reduces the number of rows passed, thus affecting the numberOfRow prop.
I'm guessing you are using TableCells something like this:
<TableCells numberOfRow={this.state.numberOfRow} numberOfColumn={this.state.numberOfColumn} />
You should change it like this:
<TableCells
numberOfRow={this.state.numberOfRow}
numberOfColumn={this.state.numberOfColumn}
onDeleteEvent={(idOfColumnToDelete) => {//Modify state here...})}
/>
You can try this one
class TableCells extends Component {
state = {
numRows = this.props.numberOfRow // I know it is considered as an antipattern :) But in this case that's ok!!!
}
onDeleteEvent = () => {
// also you should add the check if it's not zero :)
this.setState({ numRows: this.state.numRows - 1 });
};
render() {
const { numberOfColumn } = this.props; // user input
const { numRows } = this.state;
const rows = Array.from({length: numRows }).map((_, rowIdx) => (
<tr key={rowIdx}>
{
Array.from({length: numberOfColumn}).map((_, colIdx) => (
<EditableCell key={colIdx}/>
))
}
<td>
<input type="button" onClick={this.onDeleteEvent} value="X" />
</td>
</tr>
));
return (<tbody>{rows}</tbody>);
}
}
class TableCells extends Component {
constructor () {
super()
this.state = {
numberOfRow: Array.from({length: this.props.numberOfRow}),
numberOfColumn: Array.from({length: this.props.numberOfColumn})
}
}
onDeleteEvent (index) {
this.state.numberOfRow.splice(index, 1)
};
render () {
var rows = this.state.numberOfRow.map((elem, index) => (<tr>
{this.state.numberOfColumn.map((_, colIdx) => {<EditableCell key={colIdx}/>})}
<td>
<input type='button' onClick={() => this.onDeleteEvent(index)} value='X' />
</td>
</tr>))
return (
<tbody>{rows}</tbody>
)
}
}