React js useState hook. How to update state of a json object with an a array in it when a checkbox is clicked
1.Keep your initial state like this :
{
"name":"Anders",
"package":"Silver",
"email":"[email protected]",
"subject":"fdsaf",
"message":"fdsafas",
"reasons": {
"weightLoss": true,
"strength": true,
"sport": true,
}
}
- Modify onChange():
const onChange = (e) => {
if (e.target.type === 'checkbox') {
const changedReason = e.target.getAttribute('name');
setFormState({...formState, reasons:{...formState.reasons, [changedReason]: !formState.reasons[changedReason]}});
}
...
}
- Change form's
onSubmit()
:Before calling api , call converter function which will convert
formState
to JSON format required by your lambda function
const prepareDataForApi = (formData) => {
const newFormData = Object.assign({}, formData); // optional if passed cloned copy of formData object or you can also use lodash cloneDeep
newFormData.data = {
reasonArray:[]
};
Object.keys(newFormData.reasons).forEach(key => {
if(newFormData.reasons[key]){
newFormData.data.reasonArray.push({reason: key})
}
});
delete newFormData.reasons;
return newFormData;
}
Anders Kitson
Updated on June 04, 2022Comments
-
Anders Kitson almost 2 years
I am sending my state as a
stringified
variable from a form to a POST request through a lamda server which then parses it and sends it to sendgrid which there I am using send grids templating feature. Which requires me to have json Formatted like this in order to loop over one particular part (multiple checkboxes) which all should have the same key but a different value, set by areason="Weight Loss"
in the form. Here is how the eventual json needs to be formed.{ "name" :"anders", "message" : "winfsdafasfdsfsadfsadnipeg", "package" : "silver", "email" : "[email protected]", "subject" : "fdsafas", "data":{ "reasonArray":[ { "reason":"weightLoss" }, { "reason":"Sport" } ] } }
Then I can do some magic and loop over the reason's that were checked in the checkbox
<ol> {{#each data.reasonArray}} <li>{{this.reason}} </li> {{/each}} </ol>
Now I had it working if I left the state with single key value pairs and don't have the
data
portion. Here is what my initial state looked like working.const [formState, setFormState] = React.useState({ name: "", package: `${data.datoCmsPricing.title}`, email: "", subject: "", weightLoss:"", strength:"", sport:"", message: "", })
I then had the following
onChange
event that set the state with the name of the input field as the key and the value or checked state as the value. Seen hereconst onChange = (e) => { if (e.target.type === 'checkbox' && !e.target.checked) { setFormState({...formState, [e.target.name]: e.target.checked}); } else { setFormState({...formState, [e.target.name]: e.target.value }); } }
and here is my form
<form onSubmit={submitForm}> {/* <input type="text" name="package" value={data.datoCmsPricing.title} /> */} <label> Name <input type="text" name="name" value={formState.name} onChange={onChange} /> </label> <label> Email <input type="email" name="email" value={formState.email} onChange={onChange} /> </label> <label> Subject <input type="text" name="subject" value={formState.subject} onChange={onChange} /> </label> <div> <h3>Reasons for wanting to train</h3> <label> Weight Loss <input type="checkbox" name="weightLoss" checked={formState.weightLoss} onChange={onChange} /> </label> <label> Strength <input type="checkbox" name="strength" checked={formState.strength} onChange={onChange} /> </label> <label> Sport <input type="checkbox" name="sport" checked={formState.sport} onChange={onChange} /> </label> </div> <label> message <textarea name="message" value={formState.message} onChange={onChange} /> </label> <button type="submit">Submit</button> </form>
I then send it off to my lamdba function
const response = await fetch("/.netlify/functions/sendmail", { method: "POST", body: JSON.stringify(formState), })
Now my state looks like the following in json after being sent to lamdbda function and being parsed
{ name: 'Anders', package: 'silver', email: '[email protected]', subject: 'fdsafa', weightLoss: 'on', strength: 'on', sport: 'on', message: 'fdsafasf' }
Now I want to have my initial state to look like the format that sendgird wants it in, so this is what I attempted with my state setup.
const [formState, setFormState] = React.useState({ name: "", package: `${data.datoCmsPricing.title}`, email: "", subject: "", weightLoss:"", strength:"", sport:"", message: "", data:{ reasonArray:[ { reason:"" }, { reason:"" } ] } })
Then I tried to update the onChange event for the checked values with the following, I also update my form so it grabs a user friendly name for the reason. Seen below this code
const onChange = (e) => { if (e.target.type === 'checkbox' && !e.target.checked) { setFormState({...formState, data:{ reasonArray:[ { reason:e.target.reason}, ]}}); } ... }
Form Changes
... <label> Weight Loss <input type="checkbox" name="weightLoss" reason="weightLoss" checked={formState.weightLoss} onChange={onChange} /> </label> <label> Strength <input type="checkbox" name="strength" reason="strength" checked={formState.strength} onChange={onChange} /> </label> <label> Sport <input type="checkbox" name="sport" reason="sport" checked={formState.sport} onChange={onChange} /> </label> ...
The resulting Json I get after the Post request is this, with my attempt. It does not update the data part. So the resulting Json is in the right format, but it doesn't have the reason's attached. Thanks ahead of time for any help.
{ "name":"Anders", "package":"Silver", "email":"[email protected]", "subject":"fdsaf", "weightLoss":"on", "strength":"on", "sport":"on", "message":"fdsafas", "data":{ "reasonArray":[ { "reason":"" }, { "reason":"" } ] } }
Attempting Rabi's answer
... const prepareDataForApi = (formData) => { const newFormData = Object.assign({}, formData); // optional if passed cloned copy of formData object or you can also use lodash cloneDeep newFormData.data = { reasonArray:[] }; Object.keys(newFormData.reasons).forEach(key => { if(newFormData.reasons[key]){ newFormData.data.reasonArray.push({reason: key}) } }); delete newFormData.reasons; return newFormData; } const submitForm = async (e) => { e.preventDefault(); setForm(false); // const newFormData = prepareDataForApi(formData); const newFormData = prepareDataForApi(formState); console.log(newFormData); ...
-
Anders Kitson over 4 yearshmm, I get a
__proto__: Object
where thedata{Reason...
should go. -
Anders Kitson over 4 yearsI am getting a reasonArray is not defined, I must have messed up somewhere.
-
Anders Kitson over 4 yearshmm, I still end up with blank values for the
reason
-
Brian Thompson over 4 yearsAre you trying to add to the array of reasons? or trying to modify the current values?
-
Anders Kitson over 4 yearswell I would like to do both. I thought I had to have the array in the state to start. But If I could add the array after and change the value of
reason
that would be ideal. If that makes sense. I want Reason to change to a value ofWeight Loss
orSport
all depending on what was checked in checkbox. But it needs the spaces in between (depending) so it's more readable in the email in the end -
Brian Thompson over 4 yearsCan it be multiple reasons?
-
Anders Kitson over 4 yearslike in the json at the very bottom of my question
-
Anders Kitson over 4 yearsso do I put the prepareDataForApi function outside or inside my form submit handler? and then I console.log(newFormData) to test?
-
Rabi jha over 4 yearsKeep it outside of submit handler , just call
prepareDataForApi
inside submit handler, something like this: ` prepareDataForApi = () => { func def .... } onSubmit = () => { const newFromData = prepareDataForApi(formData); console.log(newFromData); } ` -
Anders Kitson over 4 yearsThe do I console log
newFormData
inside like thisprepareDataForApi = () => {console.log(newFormData);}
? -
Rabi jha over 4 yearsYou can do that if you want to print the newFromData in browser console.
-
Anders Kitson over 4 yearsyeah i just want to debug it first before I get to the next step. I will try that
-
Rabi jha over 4 yearsSure, makes sense. I have not tested the code but hopefully it should work.
-
Rabi jha over 4 yearsYou have to modify all the checkbox to something like this: <input type="checkbox" name="weightLoss" reason="weightLoss" checked={formState.reasons.weightLoss} onChange={onChange} />
-
Anders Kitson over 4 yearsI added my answer above with my attempt, I am missing where to put the
prepareDataForApi = () => {func dev ...}
hopefully you can see my mistake -
Rabi jha over 4 yearsPass **fromState**(this is where you are maintaining the form data) instead of formData(it's not defined anywhere); **const newFormData = prepareDataForApi(formState); **
-
Anders Kitson over 4 yearsSo I'm having one issue, now my checkboxes won't toggle, they just stay set as true?
-
Rabi jha over 4 yearsHave you modified the onChange() ?
-
Anders Kitson over 4 yearsno I am using your on change. I did a merge so that might be the issue I am going to roll back and just manually test, I'll get back to ya
-
Anders Kitson over 4 yearsLet us continue this discussion in chat.