Problem with Arrays in FormData Proxy
This is an issue which is related to #2616.
When trying to add an array of objects, the objects will be represented as [object Object].
What do I want to do?
I want to select one or multiple files.
Then, using htmx:configRequest, I want to map the files to their metadata which is sent to my endpoint.
Behavior in HTMX 1.x:
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/htmx.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/ext/json-enc.js"></script>
<script>
function convertFilesToMetadata(event) {
var files = event.detail.parameters.files;
event.detail.parameters.files = files.map((file) => ({
name: file.name,
size: file.size,
lastModified: file.lastModified,
type: file.type,
}));
}
</script>
<input id="files"
name="files"
type="file"
hx-post="/upload"
hx-trigger="change"
hx-ext="json-enc"
hx-on:htmx:config-request="convertFilesToMetadata(event)"
multiple>
Bug in HTMX 2.x:
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/htmx.min.js"></script>
<script src="https://unpkg.com/[email protected]/json-enc.js"></script>
<script>
function convertFilesToMetadata(event) {
var files = event.detail.parameters.files;
if (files.length < 1) {
// cancel event, no files selected
event.preventDefault();
return;
}
// convert files to dictionary of metadata
event.detail.parameters.files = files.map((file) => ({
name: file.name,
size: file.size,
lastModified: file.lastModified,
type: file.type,
}));
}
</script>
<input id="files"
name="files"
type="file"
hx-post="/upload"
hx-trigger="change"
hx-ext="json-enc"
hx-on:htmx:config-request="convertFilesToMetadata(event)"
multiple>
This will return undefined, since the Proxy does not implement map.
Changing map to a simple for, will result in the issue of [object Object]:
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/htmx.min.js"></script>
<script src="https://unpkg.com/[email protected]/json-enc.js"></script>
<script>
function convertFilesToMetadata(event) {
console.log(event);
var files = event.detail.parameters.files;
if (files.length < 1) {
// cancel event, no files selected
event.preventDefault();
return;
}
var metadata = new Array(files.length);
for (let index = 0; index < files.length; index++) {
let file = files[index];
metadata[index] = {
name: file.name,
size: file.size,
lastModified: file.lastModified,
type: file.type,
}
}
event.detail.parameters.files = metadata;
}
</script>
<input id="files"
name="files"
type="file"
hx-post="/upload"
hx-trigger="change"
hx-ext="json-enc"
hx-on:htmx:config-request="convertFilesToMetadata(event)"
multiple>
Applying, JSON.stringify on the object or metadata does not help because then it will be parsed as a string of JSON and not the object itself.
Trying to assigning it directly event.detail.parameters.files[index] also does not work.
Other issues/breaking changes:
- A single file is now the file itself and not an array as before in 1.x
- Assigning an array with 1 element to the formData will also unpack the array
- In the other issue #2616, there is also a mention that the
FormDataobject can be accesses viaevent.detail.formData, however, upon inspection it will be empty.
A quick test with event.detail.formData seems to be working for me. can you try console.log(Array.from(event.detail.formData.entries())) maybe?
That shows me the values. What confuses me is that logging/inspecting event.detail.formData does not show anything in the console:
Yeah FormData is a bit different and you need to use .entries() and treat it as an iterable or .get() or .getAll().
Can you try using event.detail.parameters.getAll('files') to return the proper array of files value and see if this makes it easier to operate on?
function convertFilesToMetadata(event) {
console.log(event);
var files = event.detail.parameters.getAll('files');
if (files.length < 1) {
event.preventDefault();
return;
}
// Collect metadata for all files
const filesMetadata = files.map(file => ({
name: file.name,
size: file.size,
lastModified: file.lastModified,
type: file.type,
}));
event.detail.parameters.files = JSON.stringify(filesMetadata);
}
This seems to work with JSON.stringify()
FormData cannot accept plain JS objects directly—must use strings or Blob/File.
Yes, this makes it easier and more similar to the old behavior. I figured that I will to have parse the data from a JSON string or array of strings in my backend.
I guess the problem also comes from the json-enc extension or the attempt to send JSON. One could modify this extension to fully restore the old behavior but for me its good now. I just would have been happy to find this behavior change in the migration guide or somewhere else in the documentation :)