Search code examples
urlencodex-www-form-urlencodednetlify-form

How to encode and parse / decode a nested query string Javascript


I'm sending form data from React Hook Form to Netlify via their submission-created function. I don't have any problem with encoding individual form field values, but now I'm trying to encode an array of objects.

Here is an example of my form data:

{
  _id: "12345-67890-asdf-qwer",
  language: "Spanish",
  formId: "add-registration-form",
  got-ya: "",
  classType: "Private lessons",
  size: "1",
  days: [
    {
      day: "Monday",
      start: 08:00",
      end: "09:30"
    },
    {
      day: "Wednesday",
      start: "08:00",
      end: "09:30"
    }
  ]
}

The only problem I have is with the "days" array. I've tried various ways to encode this and this is the function I've currently been working with (which isn't ideal):

 const encode = (data) => {
    return Object.keys(data).map(key => {
      let val = data[key]
      if (val !== null && typeof val === 'object') val = encode(val)
      return `${key}=${encodeURIComponent(`${val}`.replace(/\s/g, '_'))}`
    }).join('&')
  }

I tried using a library like qs to stringify the data, but I can't figure out how to make that work.

And here is the function posting the data to Netlify:

// Handles the post process to Netlify so I can access their serverless functions
   const handlePost = (formData, event) => {
    event.preventDefault()
    fetch(`/`, {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: encode({ "form-name": 'add-registration-form', ...formData }),
    })
      .then((response) => {
        if(response.status === 200) {
          navigate("../../")
        } else {
          alert("ERROR!")
        }
        console.log(response)
      })
      .catch((error) => {
        setFormStatus("error")
        console.log(error)
      })
  }

Finally, here is a sample of my submission-created file to receive and parse the encoded data:

const sanityClient = require("@sanity/client")
const client = sanityClient({
  projectId: process.env.GATSBY_SANITY_PROJECT_ID,
  dataset: process.env.GATSBY_SANITY_DATASET,
  token: process.env.SANITY_FORM_SUBMIT_TOKEN,
  useCDN: false,
})

const { nanoid } = require('nanoid');

exports.handler = async function (event, context, callback) {
  
  // Pulling out the payload from the body
  const { payload } = JSON.parse(event.body)

  // Checking which form has been submitted
  const isAddRegistrationForm = payload.data.formId === "add-registration-form"

  // Build the document JSON and submit it to SANITY
  if (isAddRegistrationForm) {


    // How do I decode the "days" data from payload?
    let schedule = payload.data.days.map(d => (
    {
      _key: nanoid(),
      _type: "classDayTime",
      day: d.day,
      time: {
        _type: "timeRange",
        start: d.start,
        end: d.end
      }
    }
  ))


    const addRegistrationForm = {
      _type: "addRegistrationForm",
      _studentId: payload.data._id,
      classType: payload.data.classType,
      schedule: schedule,
      language: payload.data.language,
      classSize: payload.data.size,
    }
    const result = await client.create(addRegistrationForm).catch((err) => console.log(err))
  }
  
  callback(null, {
    statusCode: 200,
  })
}

So, how do I properly encode my form data with a nested array of objects before sending it to Netlify? And then in the Netlify function how do I parse / decode that data to be able to submit it to Sanity?


Solution

  • So, the qs library proved to be my savior after all. I just wasn't implementing it correctly before. So, with the same form data structure, just make sure to import qs to your form component file:

    import qs from 'qs'
    

    and then make your encode function nice and succinct with:

         // Transforms the form data from the React Hook Form output to a format Netlify can read
          const encode = (data) => {
            return qs.stringify(data)
          } 
    

    Next, use this encode function in your handle submit function for the form:

     // Handles the post process to Netlify so we can access their serverless functions
       const handlePost = (formData, event) => {
        event.preventDefault()
    
        fetch(`/`, {
          method: "POST",
          headers: { "Content-Type": "application/x-www-form-urlencoded" },
          body: encode({ "form-name": 'add-registration-form', ...formData }),
        })
          .then((response) => {
            reset()
            if(response.status === 200) {
              alert("SUCCESS!")
            } else {
              alert("ERROR!")
            }
            console.log(response)
          })
          .catch((error) => {
            console.log(error)
          })
      }
    

    Finally, this is what your Netlify submission-created.js file should look like more or less:

    const sanityClient = require("@sanity/client")
    const client = sanityClient({
      projectId: process.env.GATSBY_SANITY_PROJECT_ID,
      dataset: process.env.GATSBY_SANITY_DATASET,
      token: process.env.SANITY_FORM_SUBMIT_TOKEN,
      useCDN: false,
    }) 
    const qs = require('qs')
    const { nanoid } = require('nanoid');
    
    exports.handler = async function (event, context, callback) {
      
      // Pulling out the payload from the body
      const { payload } = JSON.parse(event.body)
    
    
      // Checking which form has been submitted
      const isAddRegistrationForm = payload.data.formId === "add-registration-form"
    
      // Build the document JSON and submit it to SANITY
      if (isAddRegistrationForm) {
        const parsedData = qs.parse(payload.data)
        
        let schedule = parsedData.days
          .map(d => (
            {
              _key: nanoid(),
              _type: "classDayTime",
              day: d.day,
              time: {
                _type: "timeRange",
                start: d.start,
                end: d.end
              }
            }
          ))
    
        const addRegistrationForm = {
          _type: "addRegistrationForm",
          submitDate: new Date().toISOString(),
          _studentId: parsedData._id,
          classType: parsedData.classType,
          schedule: schedule,
          language: parsedData.language,
          classSize: parsedData.size,
        }
        const result = await client.create(addRegistrationForm).catch((err) => console.log(err))
      }
      
      callback(null, {
        statusCode: 200,
      })
    }