How to grab checkboxes and put them into state? - reactjs

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>
);
};

Related

How to save changes of a input in table row as single and multiple?

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 think I'm missing something fairly basic about useState() hook in ReactJS?

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

How to disable current input box, from mapped input boxes

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.

Is there something wrong with my react parent class?

I am practicing using react to build a simple table. Here my table has three columns. (name, job, delete). There is already some data in the table. In the third column, I want to build a button so the user can click and cancel the whole row
I already fixed several bugs but the table still does not show up
const TableBody = props => {
const rows = props.fillTheData.map((row, index) => {
return (
<tr key={index}>
<td>{row.name}</td>
<td>{row.job}</td>
<td><button onClick={() => props.removeCharacter(index)}>Delete</button></td>
</tr>
);
});
return <tbody>{rows}</tbody>;
}
class App extends React.Component {
state = {
character : [ ]
};
removeCharacter = index => {
const {character} = this.state;
this.setState({
character: character.filter((element, i) => {
return i !== index;
})
});
}
handleSubmit = character => {
this.setState({character:[...this.state.character,character]})
}
render() {
return(
<div class= "container">
<Table characters = {this.state.character} removeCharacter = {this.removeCharacter} />
<Form handleSubmit = {this.handleSubmit}/>
</div>
)
}
}
class Form extends React.Component {
constructor (props) {
super( props );
this.initialState = {
name: '',
job: ''
};
this.state = this.initialState;
}
handleChange = event => {
const{name,job} = event.target;
this.setState(
{
[name]: value
}
);
}
submitForm = () => {
this.props.handleSubmit(this.state);
this.setState(this.initialState);
}
render() {
const { name, job } = this.state;
return (
<div class="container2">
<form>
<label>Name</label>
<input
type="text"
name="name"
value={name}
onChange={this.handleChange} />
<label>Job</label>
<input
type="text"
name="job"
value={job}
onChange={this.handleChange}/>
</form>
<input
type="button"
value="Submit"
onClick={this.submitForm} />
</div>
);
}
}
export default Form;
class Table extends React.Component {
render(){
const {characters, removeCharacter} = this.props;
return(
<table>
<TableHeader />
<TableBody fillTheData = {characters} removeCharacter= {removeCharacter} />
</table>
)
}
}
const TableHeader = () => {
return (
<thead>
<tr>
<th>Name</th>
<th>Job</th>
<th>Delete</th>
</tr>
</thead>
);
}
Right now, we have a cool Table component, but the data is being hard-coded. One of the big deals about React is how it handles data, and it does so with properties, referred to as props, and with state. First, we’ll focus on handling data with props.
Then let’s move all that data to an array of objects, as if we were bringing in a JSON-based API. We’ll have to create this array inside our render().
The handleChange method of Form component is declaring a constant named job which doesn't exist in event.target object and it is setting the state with value variable which doesn't exists in that scope.
So change
const{name,job} = event.target;
to
const{name,value} = event.target;
your code will just work fine.

How to map() when state "selectedOptions2" triggered

i need you to look for bug my code. I know it is something wrong.
import React, { Component } from "react";
import { options1, options2, questions } from "./data";
import Select from "react-select";
class Esensial extends Component {
constructor(props) {
super(props);
this.state = {
selectedOption: [],
selectedOption2: []
};
}
handleChange1 = selectedOption => {
this.setState({ selectedOption, selectedOption2: null });
};
handleChange2 = selectedOption => {
this.setState({ selectedOption2: selectedOption });
};
filteredOptions() {
return options2.filter(o => o.link ===
this.state.selectedOption.value);
}
questionsOptions() {
return questions.filter(
question => question.link === this.state.selectedOption2.value
);
}
render() {
const filteredOptions = this.filteredOptions();
const questionsOptions = this.questionsOptions();
return (
<div>
<h1>UKM Esensial</h1>
<div className="form-group">
<label>Pilih Jenis Upaya Pelayanan Kesehatan</label>
<Select
className="form-control"
isClearable={false}
onChange={this.handleChange1}
options={options1}
value={this.state.selectedOption}
/>
</div>
<div className="form-group">
<label>Pilih Variable</label>
<Select
className="form-control"
isClearable
onChange={this.handleChange2}
options={filteredOptions}
value={this.state.selectedOption2}
/>
</div>
<div>
<table className="table table-striped">
<thead>
<tr>
<th>Sub-Variabel</th>
<th>Sasaran</th>
<th>Capaian</th>
</tr>
</thead>
<tbody>
{questionsOptions.map(q => (
<tr>
<td>{q}</td>
<td>
<input type="number" />
</td>
<td>
<input type="number" />
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
}
}
export default Esensial;
the code will run, when selectedOption get a value it will filter() the options2, when selectedOption2 get a value it will filter() the questionsOptions, but i don't know the code still crash.. help me please..
is it wrong with the map?
may be because i don't use componentDidUpdate(){}.
somehow when i use componentDidUpdate(), it execute by itself. maybe i use firebaseauth() to authenticate the login system.
This is because of this.state.selectedOption2.value, as you are setting value of selectedOption2 in handleChange1 null. Following can be solutions:
questionsOptions() {
const {selectedOption2} = this.state;
return questions.filter(
question => question.link === selectedOption2 && selectedOption2.value || ''
);
}
From your initial state I am assuming that selectedOption2 is array so you cannot access value as selectedOption2.value you have to access by index e.g selectedOption2[0].value(assuming 0 in the required index)
Your state is a bit confusing, because you've initialized them as arrays, but you'd like to access them as objects. The error message evinces that selectedOption2 doesn't own any property called value.
The answer strongly depends on what you expect to get when the onChange method is effected on.
I don't know your exact data structures, but if you're working with arrays, then the filter functions should be something similar to this:
questionsOptions() {
return questions.filter(
question => {
this.state.selectedOption2.map(option => {
return question === option.value;
});
}
);
}
In the aforementioned example, I assume that selectedOption2 is an array of objects, therefore you should use the map function in order to find the corresponding value that is expected.
By the way, if it's a simple value - i.e. it's a string, integer etc. -, then you should leave the value property on the right side of the comparison in your example.
questionsOptions() {
return questions.filter(
question => question === this.state.selectedOption2
);
}
On the contrary, if it's an array with simple values, then:
questionsOptions() {
return questions.filter(
question => {
this.state.selectedOption2.map(option => {
return question === option;
});
}
);
}
I'd also propose to look into your data structures and think about how the state field should be initialized, since it's quite misleading and causes impediments both for the readers and you.

Resources