How to use JavaScript’s FormData Object to Send Complex Data

Daniel Pericich
9 min readOct 30, 2022

--

Photo by Brett Jordan on Unsplash

HTTP requests are worthless without data attached to them. At the most basic level the data can consist of the HTTP method and a message status. A GET request can allow us to retrieve a user record and a POST request may enable us to create an order record for our store front.

These requests are easy to complete, especially, with libraries such as axios and JavaScript’s built in fetch API. However, formatting the data for a POST request body can become challenging as the complexity of the record, or set of records, in a form increases. Multipart forms that require a specific structure to support multi record creation can become unmanageable. They become even more of an issue when we decide we want to include nested File objects within them.

To help make this process easier, JavaScript introduced the FormData object. This object allows you to create JavaScript objects, either through construction or composition, which support the transfer of both string and File type values. Supporting File objects is a major benefit of FormData as the common method of calling JSON.stringify on your HTTP request body will cause you to lose the binary encoding of your File object and its associated properties.

As I mentioned briefly, FormData supports object creation through both composition and construction. These are very different approaches, and though they can be used together, which method you use should be a conscious decision when you are trying to turn your web form data into a POSTable request body.

The FormData Object

You may see XML in some data transfer requests, but HTTP methods largely use JSON to handle transferring data. This allows them to have a versatile handshake between servers speaking different languages. Yes, the “J” in JSON stands for JavaScript, but this data format is mostly language agnostic making it a great tool for sending data between any type of client and server.

The structure of the FormData object is very simple. It is meant to be used as the body argument of HTTP requests and thus is modeled after JSON. However, where JSON uses hashes with key value pairs, FormData creates an array of arrays to mimic JSON structures. In the main array, each child array has two items: a key and value:

Figure 1. Basic FormData key structure.

Well, this simple requirement of a two item structure is mostly true. What makes FormData so useful for web forms is its support for object values beyond simple strings, numbers and booleans. Along with primitive types, FormData also supports Blob type objects as values. While many developers have never worked directly with Blob types, they probably have indirectly as Blobs are a superset of File type objects. Therefore, FormData objects support the storage and transfer of File type objects.

When we have simple FormData sets we specify a key name and a simple primitive type value, but with File objects we have to specify three arguments. Those arguments are a key name, the File object and finally a filename. You can always retrieve the filename later from the File’s name attribute, but must specify it separately anyways when adding a key to your FormData object instance per FormData’s specs:

Figure 2. FormData key structure for File values.

Now that we know the acceptable child elements of the FormData object, we need to look at how to add and remove child elements. Depending on if you use construction or composition to create your FormData object, you may start with an empty FormData instance object. To be able to populate and modify this FormData instance we have three methods to be able to add and remove data to our objects.

The first method, delete, is the most straight forward. With this instance method you specify the key name of the FormData key you’d like to remove and it’s gone. Adding entries to FormData is a little more complicated.

We previously talked about the required arguments for adding to our FormData object, but need to discuss how we add key value pairs to our FormData instance. The FormData object has two addition instance methods which are append and set. Both allow us to specify keys and values to add to our FormData object, but have subtle differences.

The append method allows us to specify a key we would like to add with a specific value, or a key to add a value to. That’s right, using append with a key that already exists won’t overwrite the current value, but instead will add the value onto the existing value:

Figure 3. Adding keys to FormData instance using append method.

The set method differs here as if we attempt to assign a key value pair with an existing key, we will simply override the existing key’s value. This is important if we are going to modify a FormData key in place and don’t want to worry about data collisions:

Figure 4. Adding keys to FormData instance using set method.

We’ve discussed what the FormData object looks like and addition and removal instance methods to modify it. While I showed the “getAll” method, which returns all values related to a key, sometimes you want to be able to view all items of your FormData object. If you console.log the FormData object you will not see the values of the object, but instead see “FormData {}”.

In order to view all the key value pairs in your FormData instance you can run the following cod in your console:

Figure 5. Helper for loop for logging all key value pairs of a FormData instance.

Building FormData by Composition

Composition is an important pattern for any programing language. With Object Oriented Programming it is common to have issues with multi level or multi object inheritance. These items can make attributes and class and instance methods confusing to work with. You always wonder if parent element’s methods and data will collide with our current object’s values?

We can use Composition to solve this by creating small, modular sets of functionality that we can then explicitly choose to include in our code. Initializing a massive object could include all sorts of unwanted data and behavior. With composition we are fully in control of what data and functionality our object has.

Building HTTP request body objects with empty FormData initializations allows for a similar approach. The FormData object can accept one or zero arguments when it is initialized. The only argument it can accept is a reference to an HTML form element, but we will wait to touch on that in the next section. For this section we would like to work with an empty FormData initialization and talk about the use case for this approach.

When we call “new FormData()” in our code we create an empty FormData instance. Empty in the sense that it is an array without any child arrays. This empty object can be useful if you have simple state with a flat structure in a state driven web app built with a library such as React. Instead of storing all the input in the input elements themselves, we can delegate maintaining input values to our component’s state.

This works great if you want to bypass some of the default behavior of web forms or avoid storing data in the input elements. As your user fills out the form you can compose your FormData object for use in the HTTP request by calling append and set. This offers maximum control to the point that you do not need to worry about removing unneeded key value pairs.

The downside of building the FormData object by composition is it limits how complex our HTTP body object’s structure can be. A key point of FormData child elements is that each child element can only have a value that is a string or a Blob. This doesn’t give us a lot of room to handle non primitive or nested Blob structures.

If we only need primitive key values, or even keys that are arrays of objects in which each value is a primitive then we are good to use composition. To accomplish this we can create the objects and call JSON.stringify() on these objects to be able add the key value pair to our FormData object.

If we want to add an array of File objects for something like product images for a store front or scanned images for an insurance claim then we run into an issue. Again, we can only store values in our FormData object that are strings or Blobs. If we attempt to JSON.stringify() an array of File objects, the result we will get is a string value of an array of empty hashes.

This is not the intended or desired outcome, so we will need to find a different way. This is where building FormData by construction comes in.

Building FormData by Construction

Without using JavaScript we can create multipart forms through simple HTML. Just wrap your input elements in a form element and when you click an input element with a type of submit, all values in the input fields will be posted to the specified endpoint.

This works, but does not give us a lot of control of what is sent. If we had conditional hidden fields that should or shouldn’t be included, we can’t handle this logic with simple HTML. To get more control we can use the inputs and the values contained within our form element to create a FormData object instance. Remember, the FormData object can take zero or one arguments at initialization. By supplying a reference to our form element, we can create an instance of our FormData object, including the structure of the input elements.

An important note with using a form element to construct your FormData object is that all child input elements must have their name attribute specified. This name attribute is what is used create the key for our FormData child sets.

To get a better feel for how to create FormData instances through construction I will walk through both an HMTL and Vanilla JS solution as well as a React implementation. The keys for correctly implementing these methods is to wrap all required input fields in the same form element, add a name attribute to each input field and have a way to reference the html element from your JavaScript code.

Construction with HTML and Vanilla JavaScript

This method requires more general knowledge of JavaScript rather than library specific methods. With the Vanilla JavaScript approach we lay out our HTML as usual, including an id for our form element. We are then able to access the contents of this form using “getElementById.” The full code snippet can be seen here:

Figure 6. FormData Construction with Vanilla JavaScript.

A point to notice here is our file input element. While FormData by composition limits us to flat structures for adding files, the construction method allows us to have complex collections of File objects.

Depending on how we setup our input fields’ names, we can create flat or deeply nested FormData structures. The name dictates the structure, not the way in which the HTML is formatted.

Construction with React

For this method we have to make use of some of React’s hooks. React uses a synthetic DOM instead of the actual DOM for representing the HTML you work with. Because of this we do not have access to DOM manipulation and selector methods such as “getElementById” or “querySelector.”

Here is how the previous code would look in React:

Figure 7. FormData Construction with React.

The main difference lays not in the HTML, but in the way we reference the elements. In order to access our form element we make use of the useRef hook. This hook allows us to get the current value of an element, specifically our form element and its value.

Conclusion

Creating multi part forms doesn’t have to be difficult, and I hope that these two methods of building FormData with composition and construction prove that. As with any task, choosing the correct tool makes all the difference. I hope that this article has helped you understand why you’d want to build your FormData objects with the correct method.

Notes

https://developer.mozilla.org/en-US/docs/Web/API/FormData

https://developer.mozilla.org/en-US/docs/Web/API/File

https://reactjs.org/docs/hooks-reference.html#useref

--

--

Daniel Pericich
Daniel Pericich

Written by Daniel Pericich

Former Big Beer Engineer turned Full Stack Software Engineer

No responses yet