React js useState hook. How to update state of a json object with an a array in it when a checkbox is clicked

14,930

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,
   }
}
  1. 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]}});
    } 
    ...
 }

  1. 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;
}
Share:
14,930
Anders Kitson
Author by

Anders Kitson

Updated on June 04, 2022

Comments

  • Anders Kitson
    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 a reason="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 here

    const 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
    Anders Kitson over 4 years
    hmm, I get a __proto__: Object where the data{Reason... should go.
  • Anders Kitson
    Anders Kitson over 4 years
    I am getting a reasonArray is not defined, I must have messed up somewhere.
  • Anders Kitson
    Anders Kitson over 4 years
    hmm, I still end up with blank values for the reason
  • Brian Thompson
    Brian Thompson over 4 years
    Are you trying to add to the array of reasons? or trying to modify the current values?
  • Anders Kitson
    Anders Kitson over 4 years
    well 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 of Weight Loss or Sport 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
    Brian Thompson over 4 years
    Can it be multiple reasons?
  • Anders Kitson
    Anders Kitson over 4 years
    like in the json at the very bottom of my question
  • Anders Kitson
    Anders Kitson over 4 years
    so do I put the prepareDataForApi function outside or inside my form submit handler? and then I console.log(newFormData) to test?
  • Rabi jha
    Rabi jha over 4 years
    Keep 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
    Anders Kitson over 4 years
    The do I console log newFormData inside like this prepareDataForApi = () => {console.log(newFormData);}?
  • Rabi jha
    Rabi jha over 4 years
    You can do that if you want to print the newFromData in browser console.
  • Anders Kitson
    Anders Kitson over 4 years
    yeah i just want to debug it first before I get to the next step. I will try that
  • Rabi jha
    Rabi jha over 4 years
    Sure, makes sense. I have not tested the code but hopefully it should work.
  • Rabi jha
    Rabi jha over 4 years
    You 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
    Anders Kitson over 4 years
    I 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
    Rabi jha over 4 years
    Pass **fromState**(this is where you are maintaining the form data) instead of formData(it's not defined anywhere); **const newFormData = prepareDataForApi(formState); **
  • Anders Kitson
    Anders Kitson over 4 years
    So I'm having one issue, now my checkboxes won't toggle, they just stay set as true?
  • Rabi jha
    Rabi jha over 4 years
    Have you modified the onChange() ?
  • Anders Kitson
    Anders Kitson over 4 years
    no 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
    Anders Kitson over 4 years