Appearance
CreateFormData Service
A utility function for converting JavaScript objects into FormData instances with support for nested objects, arrays, files, and proper serialization. Essential for handling complex form submissions that include file uploads alongside regular data.
Function Signature
typescript
function createFormData(data: any): FormData;Parameters
| Parameter | Type | Description |
|---|---|---|
data | any | The object to convert to FormData |
Returns
FormData - A properly formatted FormData instance ready for HTTP submission
Features
Automatic Type Handling
- Files & Blobs: Appended directly without transformation
- Arrays: Serialized with indexed bracket notation (
key[0],key[1]) - Nested Objects: Flattened with bracket notation (
parent[child]) - Dates: Converted to ISO string format
- Primitives: Converted to strings
Null/Undefined Handling
- Skips null and undefined values
- Prevents empty or invalid entries in FormData
Recursive Processing
- Handles deeply nested structures
- Maintains proper key naming for all levels
Usage Examples
Basic Usage
typescript
import { createFormData } from "dolphin-components";
const data = {
name: "John Doe",
email: "[email protected]",
age: 30,
};
const formData = createFormData(data);
// Result:
// formData.get('name') => 'John Doe'
// formData.get('email') => '[email protected]'
// formData.get('age') => '30'With File Upload
vue
<script setup>
import { ref } from 'vue';
import createFormData from 'dolphin-components';
import axios from 'axios';
const form = ref({
title: '',
description: '',
image: null as File | null
});
const handleFileSelect = (event: Event) => {
const target = event.target as HTMLInputElement;
if (target.files?.[0]) {
form.value.image = target.files[0];
}
};
const submitForm = async () => {
const formData = createFormData(form.value);
await axios.post('/api/posts', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
});
};
</script>
<template>
<form @submit.prevent="submitForm">
<input v-model="form.title" placeholder="Title" />
<textarea v-model="form.description" placeholder="Description" />
<input type="file" @change="handleFileSelect" accept="image/*" />
<button type="submit">Submit</button>
</form>
</template>With Nested Objects
typescript
import createFormData from "dolphin-components";
const userData = {
user: {
firstName: "John",
lastName: "Doe",
address: {
street: "123 Main St",
city: "New York",
country: "USA",
},
},
settings: {
newsletter: true,
notifications: false,
},
};
const formData = createFormData(userData);
// Result:
// formData.get('user[firstName]') => 'John'
// formData.get('user[lastName]') => 'Doe'
// formData.get('user[address][street]') => '123 Main St'
// formData.get('user[address][city]') => 'New York'
// formData.get('user[address][country]') => 'USA'
// formData.get('settings[newsletter]') => 'true'
// formData.get('settings[notifications]') => 'false'With Arrays
typescript
import createFormData from "dolphin-components";
const orderData = {
customerId: 123,
items: [
{ productId: 1, quantity: 2 },
{ productId: 5, quantity: 1 },
{ productId: 8, quantity: 3 },
],
tags: ["express", "gift-wrap"],
};
const formData = createFormData(orderData);
// Result:
// formData.get('customerId') => '123'
// formData.get('items[0][productId]') => '1'
// formData.get('items[0][quantity]') => '2'
// formData.get('items[1][productId]') => '5'
// formData.get('items[1][quantity]') => '1'
// formData.get('items[2][productId]') => '8'
// formData.get('items[2][quantity]') => '3'
// formData.get('tags[0]') => 'express'
// formData.get('tags[1]') => 'gift-wrap'With Multiple Files
typescript
import createFormData from "dolphin-components";
const galleryData = {
albumName: "Vacation 2024",
images: [file1, file2, file3], // Array of File objects
metadata: {
isPublic: true,
location: "Hawaii",
},
};
const formData = createFormData(galleryData);
// Result:
// formData.get('albumName') => 'Vacation 2024'
// formData.get('images[0]') => File object
// formData.get('images[1]') => File object
// formData.get('images[2]') => File object
// formData.get('metadata[isPublic]') => 'true'
// formData.get('metadata[location]') => 'Hawaii'With Dates
typescript
import createFormData from "dolphin-components";
const eventData = {
title: "Team Meeting",
startDate: new Date("2024-06-15T10:00:00"),
endDate: new Date("2024-06-15T11:00:00"),
};
const formData = createFormData(eventData);
// Result:
// formData.get('title') => 'Team Meeting'
// formData.get('startDate') => '2024-06-15T10:00:00.000Z'
// formData.get('endDate') => '2024-06-15T11:00:00.000Z'Complete Form Example with FileUpload
vue
<script setup>
import { ref } from 'vue';
import { FileUpload, createFormData } from 'dolphin-components';
import axios from 'axios';
const formState = ref({
product: {
name: '',
description: '',
price: 0,
category: ''
},
mainImage: null as File | null,
additionalImages: [] as File[],
specifications: [
{ key: '', value: '' }
]
});
const addSpecification = () => {
formState.value.specifications.push({ key: '', value: '' });
};
const submitProduct = async () => {
const payload = {
...formState.value.product,
mainImage: formState.value.mainImage,
additionalImages: formState.value.additionalImages,
specifications: formState.value.specifications.filter(s => s.key && s.value)
};
const formData = createFormData(payload);
try {
const response = await axios.post('/api/products', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
});
console.log('Product created:', response.data);
} catch (error) {
console.error('Failed to create product:', error);
}
};
</script>
<template>
<form @submit.prevent="submitProduct" class="space-y-4">
<div>
<label>Product Name</label>
<input v-model="formState.product.name" class="input-text" />
</div>
<div>
<label>Description</label>
<textarea v-model="formState.product.description" class="input-text" />
</div>
<div>
<label>Price</label>
<input
v-model.number="formState.product.price"
type="number"
class="input-text"
/>
</div>
<div>
<label>Main Image</label>
<FileUpload v-model="formState.mainImage" accept="image/*" />
</div>
<div>
<label>Additional Images</label>
<FileUpload
v-model="formState.additionalImages"
:multiple="true"
:max-files="5"
accept="image/*"
/>
</div>
<div>
<label>Specifications</label>
<div
v-for="(spec, index) in formState.specifications"
:key="index"
class="flex gap-2 mb-2"
>
<input v-model="spec.key" placeholder="Key" class="input-text flex-1" />
<input
v-model="spec.value"
placeholder="Value"
class="input-text flex-1"
/>
</div>
<button
type="button"
@click="addSpecification"
class="btn btn-outline-secondary"
>
Add Specification
</button>
</div>
<button type="submit" class="btn btn-primary">Create Product</button>
</form>
</template>How It Works
Processing Logic
typescript
// 1. Null/undefined values are skipped
if (value === null || value === undefined) return;
// 2. Files and Blobs are appended directly
if (value instanceof File || value instanceof Blob) {
formData.append(key, value);
}
// 3. Arrays are processed with indexed keys
if (Array.isArray(value)) {
value.forEach((item, index) => {
append(`${key}[${index}]`, item);
});
}
// 4. Objects (except Date) are processed recursively
if (typeof value === "object" && !(value instanceof Date)) {
Object.keys(value).forEach((subKey) => {
append(`${key}[${subKey}]`, value[subKey]);
});
}
// 5. Dates are converted to ISO strings
if (value instanceof Date) {
formData.append(key, value.toISOString());
}
// 6. All other values are stringified
formData.append(key, String(value));Server-Side Compatibility
PHP/Laravel
Laravel automatically parses bracket notation into arrays:
php
$request->input('items'); // Returns array of items
$request->input('user.firstName'); // Access nested data
$request->file('mainImage'); // Access uploaded fileNode.js/Express
Use middleware like multer for file handling:
javascript
const multer = require("multer");
const upload = multer({ dest: "uploads/" });
app.post("/api/products", upload.any(), (req, res) => {
console.log(req.body); // Form fields
console.log(req.files); // Uploaded files
});Best Practices
Usage Recommendations
- Use for complex form submissions with mixed data types
- Combine with FileUpload component for seamless file handling
- Always set
Content-Type: multipart/form-datain HTTP requests
Error Handling
- The function handles null/undefined gracefully
- Invalid Date objects will still be converted to ISO strings
- Circular references in objects should be avoided
Performance Considerations
- Large files are not copied, just referenced
- Suitable for forms with moderate data complexity
- For very large datasets, consider chunked uploads
Common Use Cases
- File Upload Forms: Combining text data with file uploads
- E-commerce: Product creation with images and specifications
- User Profiles: Profile data with avatar upload
- CMS Systems: Content with media attachments
- Multi-part Forms: Any form requiring FormData serialization