How to get multiple checkboxes value - javascript

I am working on an admin panel.I have 3 checkboxes for permissions of 3 things. I am getting the value of checkbox and name of permission from apidata. I am having trouble updating the checkbox value and then sending it back to the api.
How can i update it? I have searched on the internet but i have not find any solution.
Here is the code.
const [permissions, setPermissions] = useState({
checklist:{
isView:false,
isEdit:false,
isDelete:false },
incidences:{
isView:false,
isEdit:false,
isDelete:false },
dashboardt:{
isView:false,
isEdit:false,
isDelete:false
}});
{PermissionList.map((item) => {
return (
<tr>
<td>
<div className="d-flex align-items-center">
<div className="ms-2">
<h6 className="mb-1 font-14">{item.permissionName}</h6>
</div>
</div>
</td>
<td>
<div className="d-flex order-actions">
<input
className="form-check-input fs-4"
type="checkbox"
id={item.permissionId}
value={eval(`permissions.${item.permissionName}.isView`)}
checked={permissions.isView}
onChange={(e)=>{
setPermissions({
isView:e.target.checked,
isEdit:false,
isDelete:false
})
}}
aria-label="..."
/>
</div>
</td>
<td>
<div className="d-flex order-actions">
<input
className="form-check-input fs-4"
type="checkbox"
id={item.permissionId}
value={permissions.isEdit}
checked={permissions.isEdit}
onChange={(e)=>{
setPermissions({
isView:false,
isEdit:e.target.checked,
isDelete:false
})
}}
aria-label="..."
/>
</div>
</td>
<td>
<div className="d-flex order-actions">
<div className="d-flex order-actions">
<input
className="form-check-input fs-4"
type="checkbox"
id={item.permissionId}
value={permissions.isDelete}
checked={permissions.isDelete}
onChange={(e)=>{
setPermissions({
isView:false,
isEdit:false,
isDelete:e.target.checked
})
}}
aria-label="..."
/>
</div>
</div>
</td>
</tr>
);
})}
And here is the snapshot of the component
And I want this in payload sent to api:
[
{
"groupId": 1,
"permissionId": 1,
"isView": false,
"isEdit": true,
"isDelete": true
}
]
While i get the groupId and permissionId from the state and component Id how can i get the rest of three updated values. And also how can i dynamically send it like only send that permission id whose value is updated and not send the rest of 2 or 1.

There is a variant of the setter that will pass the value to a thunk function. Use that in the onClick, spread it to return that object but with just the one checkbox value updated.
onClick={setPermissions((p)=>{...p, isDelete:e.target.checked})}
Also, it's very bad practice to use eval.
// Change this:
value={eval(`permissions.${item.permissionName}.isView`)}
// To this:
value={permissions[item.permissionName].isView}
EDIT:
just make a copy of the state passed into the setter, and update the value you want to change.
onClick={
(e) => {
setPermissions(p => {
const prev = { ...p }
prev.checklist.isView = e.target.checked
return prev
})
}
}
EDIT 2:
You can call a method onClick that will update your component's state and push up to the server. Something like this:
const onCheckboxChanged = async (whichProperty, whichCheckbox, checked) => {
// update your component's state and cause component to re-render
const updatedPermissions = {...permissions}
updatedPermissions[whichProperty][whichCheckbox] = checked
setPermissions(updatedPermissions)
// you can just update the contents of your state property
// directly and not change like the next line, but it will
// not cause the component to re-render so your checkboxes
// may not update.
permissions[whichProperty][whichCheckbox] = checked
// next you can notify server, spread the view/edit/delete
// keys from your permissions object into the object you're
// sending to the server.
await pushToServer(JSON.stringify(
[{
"groupId": 1,
"permissionId": getIdFromProperty(whichProperty),
...updatedPermissions[whichProperty]
}]
))
}
Something like that. And your onChange will just call that function:
onChange={(e)=>{onCheckboxChanged(whichProperty, "checklist", e.target.checked)}}
EDIT 3:
What I recommend is that you add a "test" button in your UI. When you press it, it will send the payload to the server. Just hard-code the payload. Get that working. Then make 1 checkbox, and update your code so you can change the checkbox and when you press 'test' it will send that value to your server. If you want to update the server ever time a checkbox changes, then add that functionality (but with just 1 checkbox). Then add the other two checkboxes, but only 1 property. And finally, update the code to support multiple properties. Take small steps. Get just 1 part working and then expand on it. And maybe - don't even work on this issue. Just spend 3-7 hours following some tutorials and when you return you'll get this working very quickly.

Related

With React, what is the best way to pass a useState array back and forth between components?

I had some help with a previous issue with my little project, but I have a new problem I can't seem to understand. My program takes an array of objects (call them cards), and displays an on-screen card for each element in the array. I have an edit button for each card, which should open the edit form for the chosen item, and pre-populate it with its current state - this all works.
I want to be able to edit the item, save it back in place into the array, and have that 'card' updated. This is the main component:
import React from "react";
import ReactFitText from "react-fittext";
import Editform from "./Editform";
function Displaycards({ lastid }) {
// dummy data hardcoded for now
const [cards, setCards] = React.useState([
{
id: 1,
gamename: "El Dorado",
maxplayers: 4,
freespaces: 1,
tablenum: 5,
},
{
id: 2,
gamename: "Ticket to Ride",
maxplayers: 4,
freespaces: 2,
tablenum: 3,
},
]);
const [showForm, setShowForm] = React.useState(false);
const [currentid, setCurrentid] = React.useState(0);
return (
<div className="cardwrapper">
{cards.map(({ id, gamename, maxplayers, freespaces, tablenum }) => {
return (
<div key={id}>
<div>
<div className="card">
<ReactFitText compressor={0.8}>
<div className="gamename">{gamename}</div>
</ReactFitText>
<div className="details">
<p>Setup for: </p>
<p className="bignumbers">{maxplayers}</p>
</div>
<div className="details">
<p>Spaces free:</p>
<p className="bignumbers">{freespaces}</p>
</div>
<div className="details">
<p>Table #</p>
<p className="bignumbers">{tablenum}</p>
</div>
<button type="button" className="playbutton">
I want to play
</button>
<br />
</div>
<div className="editbuttons">
<button
type="button"
className="editbutton"
onClick={() => {
setShowForm(!showForm);
setCurrentid(id);
}}
>
Edit
</button>
<button type="button" className="delbutton">
X
</button>
</div>
</div>
</div>
);
})}
{showForm && (
<div>
<Editform cards={cards} setCards={setCards} id={currentid} />
</div>
)}
</div>
);
}
export default Displaycards;
and this is the Editform.js which it calls at the bottom. As far as I can tell I'm passing my array, setter function, and id of the card I want to edit:
function Editform({ cards, setCards, id }) {
const thisCard = cards.filter((card) => card.id === id)[0];
const editThisCard = thisCard.id === id; // trying to match id of passed card to correct card in 'cards' array.
console.log(editThisCard);
function saveChanges(cardtochange) {
setCards(
cards.map(
(
card // intention is map back over the original array, and if the id matches that
) =>
card.id === id // of the edited card, write the changed card back in at its ID
? {
id: id,
gamename: cardtochange.gamename,
maxplayers: cardtochange.maxplayers,
freespaces: cardtochange.freespaces,
tablenum: cardtochange.tablenum,
}
: card // ... or just write the original back in place.
)
);
}
return (
<>
{editThisCard && ( // should only render if editThisCard is true.
<div className="form">
<p>Name of game:</p>
<input type="text" value={thisCard.gamename}></input>
<p>Max players: </p>
<input type="text" value={thisCard.maxplayers}></input>
<p>Free spaces: </p>
<input type="text" value={thisCard.freespaces}></input>
<p>Table #: </p>
<input type="text" value={thisCard.tablenum}></input>
<p></p>
<button
type="button"
className="playbutton"
onClick={saveChanges(thisCard)} //remove to see edit form - leave in for perpetual loop.
>
Save changes
</button>
</div>
)}
</>
);
}
export default Editform;
If I comment out the onClick for the button, the page renders. If it's in there, the page gets stuck in an infinite loop that even React doesn't catch.
The way I'm trying to recreate my array is based on advice I've read here, when searching, which said to take the original array, rebuild it item-for-item unless the ID matched the one I want to change, and to then write the new version in.
I suspect there might be a way to do it with my setter function (setCards), and I know there's an onChange available in React, but I don't know a) how to get it to work, or b) how - or even if I need to - pass the changed array back to the calling component.
Your function is invoked directly upon components render:
{saveChanges(thisCard)}
Rename it to a callback style signature:
{() => saveChanges(thisCard)}
Also do add a jsfiddle/ runnable snippet for answerers to test ✌️.
Edit:
About the array of objects passing and updates, at your part the code is good where filter is used. We can apply idea of moving update logic to parent where data is located.
Now id + updated attributes could be passed to the update callback in child.
To give you hint, can use spread operator syntax to update items out of existing objects.

lit-html: issue w/ using change event to propagate changes back to data model

In the below code, i am looping through a list of my data model (this.panel), and generating an input box for each. I would like to propagate any changes made by the user back to the model.
to do this i am storing the index of the item im rendering in the html element, and adding a change event handler which will update the model using that index. The problem im having is the #change event at run time is throwing an error:
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'updateModel')'
What's odd is i have another event handler, updateToggleState in a check box lower down that works fine. Any thoughts on what is going on here?
Here is the code:
class Panel extends LitElement {
static properties = {
is_fetch_on_change: {},
};
constructor() {
super();
this.panel = [
{
"id": "ticker",
"type": "str",
"place_holder": "ticker (i.e. YHOO:^NDX)",
"value": "YHOO:^NDX",
"fn_param_name": "ticker"
},
{
"id": "st_dt",
"type": "datetime",
"place_holder": "st_dt (i.e. 2012-01-01)",
"value": "2012-01-01",
"fn_param_name": "st_dt"
},
{
"id": "end_dt",
"type": "datetime",
"place_holder": "end_dt (i.e. 2022-01-01)",
"value": "",
"fn_param_name": "end_dt"
}
]
this.is_fetch_on_change = false;
}
render() {
let ret_html = []
this.panel.forEach(function(c, i){
ret_html.push(html`
<div class="m-2">
<label>${c.fn_param_name}</label>
<input type="text" class="form-control" id = "${c.id}" placeholder="${c.place_holder}" value="${c.value}"
idx="${i}" #change=${this.updateModel} />
</div>
</div>
`)
})
ret_html.push(html`
<div class="m-2">
<label class="form-check-label" for="flexCheckDefault">
<input class="form-check-input" type="checkbox" #change=${this.updateToggleState}>
Refresh on change
</label>
</div>
<button type="submit" class="btn btn-primary m-2" ?hidden=${this.is_fetch_on_change}>Submit</button>
`)
return ret_html;
}
updateModel(e){
let idx = e.target.getAttribute('idx');
//add code here to update model
}
updateToggleState(e){
this.is_fetch_on_change = e.target.checked;
}
}
customElements.define('lit-panel', Panel);
Update:
I was able to solve the problem of not being able to reference 'this' from within the foreach loop. i needed to basically 'pass it in'. so with that solved, i can update the specific item of the dictionary now.
However, that does not update the element or re-render it.
Update 2: this is what i have now. i've tested this and it does add an element to this._panel_data when text input is changed. that change however is still not reflected in the UI.
class Panel extends LitElement {
static properties() {
_panel_data:{state: true}
};
constructor() {
super();
this.id = 100;
this._panel_data = [{"id":"ticker","type":"str","place_holder":"ticker (i.e. YHOO:^NDX)","value":"YHOO:^NDX","fn_param_name": "ticker"},
{"id":"st_dt","type": "datetime","place_holder": "st_dt (i.e. 2012-01-01)","value": "2012-01-01","fn_param_name": "st_dt"}]
}
render() {
return html`<link href="https://cdn.jsdelivr.net/npm/bootstrap#5.2.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous">
${this._panel_data.map((c, i) =>
html`
<div class="m-2">
<label>${c.fn_param_name}</label>
<input type="text" class="form-control" id = "${c.id}" placeholder="${c.place_holder}" value="${c.value}"
idx="${i}" .ref_obj=${c} #change=${e => this.updateModel(e)} />
<!--test if changes made to above, are reflected here...-->
<input type="text" class="form-control" value="${c.value}" />
</div>
</div>
`
)}
`
}
updateModel(e){
let clone = {...e.target.ref_obj};
clone.value = e.target.value;
this._panel_data = [...this._panel_data, clone];
}
}
customElements.define('lit-panel', Panel);
Using an arrow function would solve the this problem
this.panel.forEach((c, i) => {
// `this` will be bound within here
});
Or you can use a regular for loop too.
Since panel is not specified as a reactive property, changing that will not automatically re-render the component. Perhaps you want to make that into an internal reactive state.
Keep in mind that since it is an array, just mutating it with this.panel[idx] = newValue won't trigger a re-render since the array reference will be the same. You need to create a new array reference or call this.requestUpdate(). More explained here https://lit.dev/docs/components/properties/#mutating-properties

how can i get dynamic value input and store it into the state

I am using react js. I am trying to get the dynamic input values from a modal, the problem is I am not able to get the input values in a proper way. I am trying to get when the user selects the size the state will save only one value small or extra-large also user can select multiple values in extra ingredirents which will store in the state.
The above image is my UI, as you can see I can select multiple values in the radio button which I don't want. I know where the problem is but didn't get any solution. I have commented on my jsx code where the problem is.
Here is the radio button and checkbox jsx code. the input values are dynamic I have hardcoded the values
// Radio buttons
{products.options.map((product, i) => {
return (
<li onChange={(e) => getItemValues(e.target, product)}>
<label className="container_radio">
Medium size
<span>+ $5.00</span>
<input
type="radio"
value={5.00}
name={options.title} // Here i have added dynamic value that is why the radio button is selecting multiple
/>
<span className="checkmark"></span>
</label>
</li>
<li onChange={(e) => getItemValues(e.target, product)}>
<label className="container_radio">
Extra Largse size
<span>+ $8.00</span>
<input
type="radio"
value={8.00}
name={'Extra Largse size'} // If I wrote like this the radio button are selecting only one
/>
<span className="checkmark"></span>
</label>
</li>
);
})}
// checkboxes, checkboxes will be more I just hardcoded only one
{product.ingredients.map((ingredient) => {
return (
<li onChange={(e) => getItemValues(e.target, product)}>
<label className="container_check">
Extra Tomato
<span>+ $2</span>
<input
type="checkbox"
value={2}
name={'Extra-Tomato'}
/>
<span className="checkmark"></span>
</label>
</li>
);
})}
Here is the function where I am trying to store the data
const [itemDetail, setItemDetail] = useState([]);
const getItemValues = (e, product) => {
setItemDetail((prevValue) => {
const existingProduct = prevValue.find(
(item) => item.title === product.title
);
if (existingProduct) {
existingProduct.options = {
...existingProduct.options,
[e.name]: e.value,
};
return prevValue;
} else {
/* new entry */
return [
...prevValue,
{ title: product.title, options: { [e.name]: e.value } },
];
}
});
};
If I select extra large and ketchup the output looks like the below code: as you can see I am getting only one value which is ketchup, I should get also the extra large value maybe there is a problem with my function.
[{options: {ketchup: '1'}
title: "ketchup}]
My expected output is:
[{title: 'Barnes Chapman', options:{extra large: '8.0', ketchup: '1.00' } }]
Also, i want if unselect the ketchup the ketchup value will be removed from the state also for the radio buttons.

The input field is not updating "address" in the array

I am creating a table which is looping through an array and displays the array in the table as list. I am displaying the address as an input field value :value="location.address"
The input fields are set to disabled and when I click on the edit button I updated the disabled property to false so that the input field can be edited. I have added a new property called editedAddress: null and set that to null which is updated to the current address property this.editedAddress = this.locations[index].address.
What I want is that when I click on the edit button, I want the address to be updated. I have added the following code for the update button but it does not work.
btnUpdate(index){
this.locations[index].address = this.editedAddress;
this.locations[index].disabled = !this.locations[index].disabled
}
Here is the full code
<template>
<div>
<table>
<tr>
<th>#</th>
<th>Locations</th>
<th>Actions</th>
</tr>
<tr v-for="(location, index) in locations" :key="index">
<td>{{index + 1}}</td>
<td>
<input type="text" :value="location.address" :disabled="location.disabled">
</td>
<td>
<div class="action-btns">
<button #click="btnEdit(index)">Edit</button>
<button #click="btnUpdate(index)">Update</button>
<button #click="btnDelete(index)">Delete</button>
</div>
</td>
</tr>
</table>
<input type="text" v-model="address">
<button #click="addBtn">Add</button>
</div>
</template>
<script>
export default {
data(){
return{
locations:[
{
address:'Mall of lahore',
disabled: true
},
{
address: 'The Post Office',
disabled: true
},
{
address: 'Mall of Dubai',
disabled: true
}
],
address: '',
editedAddress: null
}
},
methods:{
btnEdit(index){
this.locations[index].disabled = !this.locations[index].disabled
this.editedAddress = this.locations[index].address
},
btnUpdate(index){
this.locations[index].address = this.editedAddress;
this.locations[index].disabled = !this.locations[index].disabled
},
btnDelete(index){
this.locations.splice(index , 1)
},
addBtn(){
let newAddress = {
address: this.address,
disabled: true
}
this.locations.push(newAddress)
this.address = '';
}
}
}
</script>
Please let me know what I am doing wrong or if there is a better way to solve it
Your input field is bound to location.address.
So, you are not editing your editedAddress at all.
You can add #change="editedAddress = $event.target.value" to your input field to change editedAddress
<input type="text" :value="location.address" :disabled="location.disabled" #change="editedAddress = $event.target.value" >
Tip: use Vue Dev Tools or JSON.stringify to check the data in your vue app
JSON.stringify(editedAddress): {{JSON.stringify(editedAddress)}}
Here is the link playground with the fix
Since you don't explain what exactly do you mean by "I have added the following code for the update button but it does not work" I will assume that you UI is not being updated.
This is a caveats of reactivity in Vue reactive system https://v2.vuejs.org/v2/guide/reactivity.html#For-Arrays
Probably Vue is not being able to pick that there were a change in your item inside the array locations.
To make Vue understand that there was a change you can use Vue.set:
btnUpdate(index){
Vue.set(this.locations[index], 'address', this.editedAddress);
// do the same for the other line
},
After this Vue should be able to pick that there was a change and re-render the UI for you.
Another approach is to replace the entire array (but in most cases this is a bit overkill)
btnUpdate(index){
const locations = [...this.locations];
locations[index].address = this.editedAddress;
this.locations = locations;
},

Vue JS component to render checkbox inputs and send back to v-model

In my Vue JS project I'm trying to create a generic component to render a bunch of checkboxes onto the page. I need to send the value back to the component to attach a v-model on the component.
Thus far, my checkboxes all allow me to choose true/false, but I only want to send back one true value, meaning if I select 2 out of 4 checkboxes, the v-model on my custom component should have the value of true
I've rendered my checkboxes, but am struggling to get the v-model to work, where am I going wrong?
<GroupedCheckboxes :options="editor.sources" v-model="source.isChecked" />
And the component is:
<template>
<div>
<div v-for="(checkbox, index) in options" :key="index">
<input type="checkbox">
</div>
</div>
</template>
<script>
export default {
props: ['options']
}
</script>
My v-model needs to retreive the value from the group, but isn't
Issue 1: GroupedCheckboxes doesn't implement v-model
For v-model on a component to work, the component must:
Receive a value prop 1️⃣
Emit an input event with a new value 2️⃣ Since you want the value to be true only if any of the checkboxes are checked, use Array.prototype.some()
Issue 2: GroupedCheckboxes doesn't implement checkbox groups
Checkbox groups must:
Have an initial value of type Array 3️⃣
Have the same name 4️⃣
<template>
<div>
<div v-for="(checkbox, index) in options" :key="index">
<label>
<input
type="checkbox"
name="myCheckboxGroup" 4️⃣
:value="checkbox"
v-model="myValue"
#change="$emit('input', myValue.some(v => v))" 2️⃣
>
{{ checkbox }}
</label>
</div>
</div>
</template>
<script>
export default {
props: [
'options',
'value', 1️⃣
],
data() {
return {
myValue: [], 3️⃣
}
},
}
</script>
demo

Categories

Resources