I am in a middle of project, and I have an array containing id, product, price and mapped that array which returns many input field and values that I need from that array
what's happening , when I write in one of the input field it reflects the same value to all those mapped input field,
Same if with any function I execute, for example, I passed a function which disables the input tag, but on clicking the respective button, all input tag got disabled!!
Only solution I know is to use key, but how to do that, what is the correct way of passing index value of map function into key ?
and where to use that key?
Here is a smaill example of my actuall code.
function Stack() {
const productArr = [{ Product: "TV", Price: 100 }, { Product: "watch", Price: 200 }, { Product: "mobile", Price: 300 }, { Product: "Pc", Price: 400 }]
const [value, setValue] = useState("");
const [disable, setDisable] = useState(false);
const disableHandler = () => {
setDisable(true)
}
const ProductArr = productArr.map((data, index) => {
return (
<tbody key={index}>
<tr>
<td>{index + 1}</td>
<td>{data.Product}</td>
<td>{data.Price}</td>
<td key={index} > <input type="text" disabled={disable}
onChange={(e) => {
setValue(e.target.value)
}}
value={value}
/> </td>
</tr>
</tbody>
);
});
return (
<div>
<button onClick={disableHandler} > disable </button>{/* to disable current value but all got disabled */}
<table>
<thead>
<tr>
<th>Sr. no.</th>
<th>Product</th>
<th>Price</th>
<th>Input</th>
</tr>
</thead>
{ProductArr}
</table>
</div>
)
}
please let me know the correct way?
For your value, this should work. In the onChange function of the input, I copy the contect of the productArr, replace the element that has been changed with the same element with added value to it, and set the productArr to the new array
https://codesandbox.io/s/amazing-chatelet-s9u2r?file=/src/Stack.js
For the disable action, I'm not sure how this should behave. What row should be disabled when you click on it?
Edit
Updated the codesandbox with the disabled condition. If the disable button is clicked, we set the disable variable to true, so the condition is
disable && data.value to only disable rows with values.
Related
I have some inputs in every row(PreviewItemRow.js) of Table component. I get the data from Redux store. I keep PreviewItemRow changes as internal state. There is also a save button in every button whose onClick event makes an api call to server.
Problem is I want user to save(make api call) his changes as batch requests and also use should be able to save as individual row.
If I reflect changes directly to redux store changes state in redux whenever user presses a button in keyboard, I wont be able to be sure if changes reflected to server.
If I keep the name as component internal state, I can not track changes from SaveAll button.
So how can I Implement to save changes from a button individual row and a button in parent component ?
Parent Table Component
const ParentTableComp = (props) => {
const cases = useSelector(store => store.taskAppReducer.Case.cases);
const handleSaveAllClick = () => {
dispatch(previewBulkSave({
taskId: selectedTask.taskId,
caseData: cases.map(item => ({
name: item.caseName,
}))
}))
.then(() => {
saveSuccess("All saved.");
})
.catch((err) => {
saveError(err);
});
};
return (
<div>
<Button
type='button'
color='primary'
onClick={handleSaveAllClick}
>
Save All
</Button>
<Table>
<thead>
<tr>
<th>Name</th>
</tr>
</thead>
<tbody>
{cases.map((item, index) => (
<tr key={item.caseId}>
<PreviewCaseItem
case={item}
/>
</tr>
))}
</tbody>
</Table>
</div>
);
};
This is the Row component.
const PreviewItemRow = (props) => {
const [name, setName] = useState(props.case.name)
const dispatch = useDispatch();
const handleSaveButtonClick = () => {
dispatch(saveCase({
taskType: taskType,
updatedCase: {
...props.case,
name
},
}))
.then(() => {
saveSuccess("Case Updated");
})
.catch((err) => {
saveError(err);
});
};
const handleNameChange = (event) => {
setName(event.target.value)
}
return (
<div>
<td style={{ width: 100 }}>
<Input
type={"text"}
id={`name-${props.case.caseId}`}
value={name}
onChange={handleNameChange}
/>
</td>
</div>
);
};
I am presenting my user with an editable table pre-populated with data, and want to include a 'Discard Changes' button that will call the handleReset() function to restore the 'active' set of data (an array of objects) to it's original state.
The approach I've taken is to read the data into a variable called 'dataBackup', and also into a state variable called 'dataWorking'. The idea is that user edits will be applied to dataWorking, while dataBackup will be left unchanged and is available for use to reset dataWorking if needed. However when the table is edited, changes are simultaneously applied to both variables!
I'm quite new to React and am not understanding this behavior. I'm also not certain my chosen approach is the best way to handle this functionality, because I haven't been able to find any similar examples out there. Would appreciate some pointers, and have added my sample code belong to demonstrate what's happening.
import {useState} from 'react';
const budgetData =
[
{ index: 0, category: 'Housing', item: 'Mortgage', amount: 650.99 },
{ index: 1, category: 'Housing', item: 'Insurance', amount: 275.50 },
{ index: 2, category: 'Utilities', item: 'Hydro', amount: 70.00 }
];
function UpdateBudget() {
const dataBackup = [...budgetData];
const [dataWorking, setDataWorking ] = useState(budgetData);
const handleChange = ( (e, row) => {
let selectedData = [...dataWorking];
const {name, value} = e.target;
selectedData[row][name] = value;
setDataWorking(selectedData);
});
const handleReset = ( (e) => {
setDataWorking(dataBackup);
});
return (
<div>
<table>
<thead>
<tr>
<th>Category</th> <th>Item</th> <th>Amount</th>
</tr>
</thead>
<tbody>
{ dataWorking.map( (row) => (
<tr key={row.index}>
<td> <input value={row.category} name="category" onChange={(e) => handleChange(e, row.index)}/> </td>
<td> <input value={row.item } name="item" onChange={(e) => handleChange(e, row.index)}/> </td>
<td> <input value={row.amount } name="amount" onChange={(e) => handleChange(e, row.index)}/> </td>
</tr>
))
}
</tbody>
</table>
<div>
<button onClick={ (e) => handleReset (e)}> Discard Changes </button>
</div>
<div style={{ marginTop: 20, fontSize: 10 }}> * budgetData {budgetData.length} * {JSON.stringify(budgetData)} </div>
<div style={{ marginTop: 20, fontSize: 10 }}> * dataWorking {dataWorking.length} * {JSON.stringify(dataWorking)} </div>
<div style={{ marginTop: 20, fontSize: 10 }}> * dataBackup {dataBackup.length} * {JSON.stringify(dataBackup)} </div>
</div>
);
}
export default UpdateBudget;
You are seeing dataBackup updated after your state change is because you are copying the objects inside the array by reference. If you want to have a totally unreferenced copy of budgetData, the simplest solution is
would be to use JSON methods (but I recommend researching what deep copying an object is):
const dataBackup = JSON.parse(JSON.stringify(budgetData))
Generally, best practice to store values that will not cause UI rerenders is by using useRef:
const dataBackup = React.useRef(JSON.parse(JSON.stringify(budgetData)))
// then access it like that
dataBackup.current
Another thing, inside your handleChange you are actually mutating the state, which should be avoided:
selectedData[row][name] = value;
Ideally, you should splice and spread those objects so you are not modifying the state directly, or use some deep copy method.
I am attempting to add the same array of objects to two different stateful variables using useState(), so that I can allow the user to apply changes to the first, and then discard changes and use the second to revert to the beginning state by selecting a button. I'm new to React and the result is not working out as expected! When the user types in changes, for some reason these are simultaneously applied to both, so that there is now nothing that retains the beginning state and that can be reverted to if the use selects the 'Discard Changes' button.
I've simplified the code to the following example:
import {useState} from 'react';
const budgetData =
[
{ index: 0, category: 'Housing', item: 'Mortgage', amount: 650.99 },
{ index: 1, category: 'Housing', item: 'Insurance', amount: 275.50 },
{ index: 2, category: 'Utilities', item: 'Hydro', amount: 70.00 }
];
function UpdateBudget() {
const backup = budgetData;
const [data, setData ] = useState(budgetData);
const [dataBackup ] = useState(backup);
const handleChange = ( (e, row) => {
let selectedData = [...data];
const {name, value} = e.target;
selectedData[row][name] = value;
setData(selectedData);
});
const handleReset = ( (e) => {
setData(dataBackup);
});
return (
<div>
<table>
<thead>
<tr>
<th>Category</th> <th>Item</th> <th>Amount</th>
</tr>
</thead>
<tbody>
{ data.map( (row) => (
<tr key={row.index}>
<td> <input value={row.category} name="category" onChange={(e) => handleChange(e, row.index)}/> </td>
<td> <input value={row.item } name="item" onChange={(e) => handleChange(e, row.index)}/> </td>
<td> <input value={row.amount } name="amount" onChange={(e) => handleChange(e, row.index)}/> </td>
</tr>
))
}
</tbody>
</table>
<div>
<button onClick={ (e) => handleReset (e)}> Discard Changes </button>
</div>
<div style={{ marginTop: 20, fontSize: 10 }}> * data {data.length} * {JSON.stringify(data)} </div>
<div style={{ marginTop: 20, fontSize: 10 }}> * dataBackup {dataBackup.length} * {JSON.stringify(dataBackup)} </div>
</div>
);
}
Grateful for someone to point me to point to what I'm missing here!
I have a suspicion its because cloning the array doesn't create a copy of the child elements. selectedData[row][name] this is still referencing the same value, it's a JS quirk.
const budgetData = [
["foo"],
["bar"],
["baz"],
]
const backup = budgetData
const clonedData = [...backup]
clonedData[0][0] = "abc"
console.log(backup) // backup data is mutated
Take a look at lodash cloneDeep which will also copy child values https://lodash.com/docs/4.17.15#cloneDeep
const budgetData = [
["foo"],
["bar"],
["baz"],
]
const backup = budgetData
const clonedData = _.cloneDeep(backup)
clonedData[0][0] = "abc"
console.log(backup) // backup data is not mutated
console.log(clonedData) // cloned data holds the changes
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.21/lodash.min.js"></script>
Also I don't think you need the second state as you're not altering it. You can probably ditch this const [dataBackup ] = useState(backup) and just use backup
What I tried to achieve is to make the table row clickable with input element so it will select the entire row. Only the problem is how can I add something like a border when the user checks the input field.
<tbody>
{allHotels.map((p: any, index: number) => (
<TableRow key={p.id} selectRow={true}>
{console.log(p.checked)}
<td><input type="checkbox" defaultChecked={p.checked} onClick={() => toggleCheckbox(index)} /></td>
<td>{p.name}</td>
<td>{p.address}</td>
<td>{p.rating}</td>
</TableRow>
))}
</tbody>
The toggleCheckbox function should update the state value representing the checked/unchecked row state.
Given that the selectRow prop on the TableRow component represents the row state, the code could look something like below:
function Hotels() {
const [allHotels, setAllHotels] = useState([]);
useEffect(() => {
fetch('/api/hotels')
.then((response) => response.json())
.then((hotels) => hotels.map((hotel) => ({ ...hotel, selected: false })))
.then(setAllHotels);
}, []);
const toggleCheckbox = (index) => {
const newHotels = [...allHotels];
newHotels[index].selected = !newHotels[index].selected;
setAllHotels(newHotels);
};
return (
<tbody>
{allHotels.map((hotel, index) => (
<TableRow key={hotel.id} selectRow={hotel.selected}>
<td>
<input
type="checkbox"
defaultChecked={hotel.checked}
onClick={() => toggleCheckbox(index)}
/>
</td>
<td>{hotel.name}</td>
<td>{hotel.address}</td>
<td>{hotel.rating}</td>
</TableRow>
))}
</tbody>
);
}
Please note that the code needs to be updated according to your needs. But I hope you get the idea.
My problem is the following: I have a table, where I generate a row with a person's name, details and details2 from an object in the DB. Next to each person there is a checkbox. I need to put those on check into an object in state and remove them from there when unchecked, because I am going to send this object on submit to the server.
How and what is the best way to do that?
I tried with current.value but I am going in circles and I don't think I have the correct idea.
<tr>
<td>
<table >
<tbody>
<tr>
<td>
<div>
<input type="checkbox"/>
<label>
data 1
</label>
</div>
</td>
<td data-title="Column 2">
data 2
</td>
<td >
<a href="#">
Edit
</a>
Delete
</a>
</td>
<td data-title="Column 5">
data 3
</td>
</tr>
</tbody>
</table>
</td>
</tr>```
You can have a handleChange function:
handleChange = ({ target }) => {
this.setState(prevState => ({
value: {
...prevState.value,
[target.name]: target.value
},
}));
And use it on the checkboxes (be sure to name each checkbox uniquely):
<input type="checkbox"
...
name="Checkbox1"
onChange={this.handleCheckboxChange}
/>
Initialize the state with something like this:
this.setState({
value: {},
});
Now, in the object value, you have values of all those checkboxes, and that object looks something like this:
value = {
Checkbox1: true,
Checkbox2: false,
...
}
You mention removing them from the state if they are unchecked. I suggest filtering:
const { value } = this.state;
const trueCheckboxesKeys = value.keys().filter(valueKey => value[valueKey]);
let trueCheckboxes = {};
for(i = 0; i < trueCheckboxesKeys.Length; i++) {
trueCheckboxes[trueCheckboxesKeys[i]] = true;
}
You can create an onChange event handler and record event.currentTarget.checked as it will return true or false. I would also recommend adding some kind of identifier to the input element so you can tell which element is being checked so you can track it correctly.
I've done something similar where a user checks a box and it updates state.
You'll need an event handler for the checkbox, plus componentDidUpdate to see if the checkbox value changed.
componentDidUpdate(prevProps, prevState) {
const { CHECKBOX_NAME } = this.state;
if (CHECKBOX_NAME !== prevState.CHECKBOX_NAME) {
if (CHECKBOX_NAME === true) {
this.setState({
// do whatever
});
} else {
this.setState({
// change back if necessary
});
}
}
}
handleCheckboxChange = event => {
const { checked } = event.target;
this.setState({
// mark box as checked in state
});
};
...
// checkbox
<input type="checkbox"
checked={this.state.CHECKBOX_NAME}
onChange={this.handleCheckboxChange}
/>
//
It depends on how you are fetching the data you render, does it come from props or do you get it from api, but generally, provided you have the users array, it could look smth like this:
const UserList = props => {
const [selectedUsers, setSelectedUsers] = useState([]);
// Your user data here
const userData = [
{
id: "1",
name: "User1",
details: {
/*details here */
}
}
];
// Check if user is selected
const isUserSelected = user => !!selectedUsers.find(u => u.id === user.id);
// Add user to selected or remove if already there
const selectUser = user => {
setSelectedUsers(users => {
if (isUserSelected(user)) {
return users.filter(u => u.id !== user.id);
}
return [...users, user];
});
};
return (
<table>
<tbody>
{userData.map(user => (
<tr key={user.id}>
<td>
<label htmlFor={`user_${user.id}`}>{user.name}</label>
<input
id={`user_${user.id}`}
type="checkbox"
checked={isUserSelected(user)}
onChange={() => selectUser(user)}
/>
</td>
</tr>
))}
</tbody>
</table>
);
};