Reputation: 23
I'm facing a strange issue in my Laravel Inertia.js project. I can successfully create new 'Ville' resources, including image uploads. Updates also work fine if I don't upload a new image. However, when I try to update a 'Ville' and include a new image, it seems like the entire form data isn't being sent to Laravel correctly, leading to validation failures.
Specifically, Laravel throws a "The nom field is required" error, even though the 'nom' field is definitely part of the FormData being sent.
Here's the React component (Edit.jsx
) using Inertia.js useForm
for the update:
import { useForm } from '@inertiajs/react';
// ... imports ...
export default function Edit({ auth, ville }) {
// ... (YearlyDataInput, DYNAMIC_FIELDS, etc. - as in my code) ...
const { data, setData, put, processing, errors } = useForm({
nom: ville.nom || '',
region: ville.region || '',
geographie: ville.geographie || '',
histoire: ville.histoire || '',
image: null,
nombre_annonces_analysees: ville.nombre_annonces_analysees || 0,
// ... other fields ...
...Object.fromEntries(DYNAMIC_FIELDS.map(field => [field.name, ville[field.name] || {}])),
});
const handleSubmit = (e) => {
e.preventDefault();
const formData = new FormData();
formData.append('_method', 'PUT'); // Explicitly setting PUT method
Object.entries(data).forEach(([key, value]) => {
if (key === "image" && value instanceof File) {
formData.append(key, value);
} else if (typeof value === "object" && value !== null) {
formData.append(key, JSON.stringify(value));
} else if (value !== null && value !== undefined) {
formData.append(key, value);
}
});
console.log("FormData contents:");
for (const pair of formData.entries()) {
console.log(pair[0]+ ', ', pair[1]);
}
put(route('villes.update', ville.id), formData, {
forceFormData: true, // Explicitly forcing FormData
});
};
// ... (rest of the component - as in your code) ...
}
And here's the update
method in my Laravel controller (VilleController.php
):
<?php
namespace App\Http\Controllers;
use App\Models\Ville;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Log;
use Exception;
use Illuminate\Validation\ValidationException;
use Illuminate\Support\Facades\Storage;
class VilleController extends Controller
{
// ... other methods ...
public function update(Request $request, Ville $ville)
{
try {
Log::info('perform a update action 👇');
Log::info('Raw Request Content: ' . $request->getContent());
Log::info('Request data for updating Ville:', $request->all());
Validator::make($request->all(), [
'nom' => 'required|string|max:255',
'region' => 'nullable|string|max:255',
// ... other validation rules as in your code ...
'image' => 'nullable|image|mimes:jpeg,png,jpg,webp|max:2048',
// ...
])->validate();
$villeData = $request->except('image');
$ville->update($villeData);
if ($request->hasFile('image')) {
// ... image handling as in your code ...
}
return redirect()->back()->with('success', 'Ville mise à jour avec succès.');
} catch (ValidationException $e) {
return redirect()->back()->withErrors($e->errors())->withInput();
} catch (Exception $e) {
Log::error('Error updating ville: ' . $e->getMessage());
return redirect()->back()->with('error', 'Une erreur inattendue est survenue.');
}
}
// ... rest of the controller ...
}
My route definition in web.php
looks like this:
Route::prefix('villes')->name('villes.')->group(function () {
// ... other routes ...
Route::put('/{ville}', [VilleController::class, 'update'])->name('update');
// ...
});
Here are the relevant logs and error messages:
Client-side FormData (from console.log
in Edit.jsx
):
FormData contents: Edit.jsx:79:16
_method, PUT Edit.jsx:81:20
nom, test create nw gonan be updated Edit.jsx:81:20
region, test update Edit.jsx:81:20
geographie, test up Edit.jsx:81:20
histoire, test up Edit.jsx:81:20
image, File { name: "Amlou-4.webp", lastModified: 1696953691022, webkitRelativePath: "", size: 85456, type: "image/webp" }
Edit.jsx:81:20
nombre_annonces_analysees, 5 Edit.jsx:81:20
nombre_habitants, {"2025":"4","2027":"7","2028":"4"} Edit.jsx:81:20
tension_locative, {"2025":"8","2033":"4"} Edit.jsx:81:20
tension_transactionnelle, {"2025":"5","2030":"4"} Edit.jsx:81:20
tourisme_vie_etudiante, {"2025":"4","2030":"4"} Edit.jsx:81:20
duree_moyenne_recherche_locataire, {"2025":"3","2031":"4"} Edit.jsx:81:20
prix_moyen_logements, {} Edit.jsx:81:20
Network Request Headers (from browser dev tools):
PUT http://127.0.0.1:8000/villes/29
...
Content-Type: multipart/form-data; boundary=----geckoformboundaryede855ff350cf86be037df87e95e4f78
...
Laravel Validation Error (from browser dev tools - Response):
{"component":"Villes\/Edit","props":{"errors":{"nom":"The nom field is required."},"flash":{ ... },"ville":{ ... }}, ... }
Laravel Log (laravel.log
):
[2025-02-14 11:57:54] local.INFO: perform a update action 👇
[2025-02-14 11:57:54] local.INFO: Raw Request Content: ------geckoformboundary75754b8ccae1fffa28bf0bf1f08eec31
Content-Disposition: form-data; name="nom"
test create nw gonan be updated
------geckoformboundary75754b8ccae1fffa28bf0bf1f08eec31
Content-Disposition: form-data; name="region"
test update
------geckoformboundary75754b8ccae1fffa28bf0bf1f08eec31
Content-Disposition: form-data; name="geographie"
test up
------geckoformboundary75754b8ccae1fffa28bf0bf1f08eec31
Content-Disposition: form-data; name="histoire"
test up
------geckoformboundary75754b8ccae1fffa28bf0bf1f08eec31
Content-Disposition: form-data; name="image"; filename="Amlou-4.webp"
Content-Type: image/webp
RIFF�M
I've tried a few things: ensuring FormData is correctly constructed, using forceFormData: true
in Inertia, and verifying the request headers. It really seems like Laravel isn't parsing the multipart form data properly in the update scenario when an image is included.
Could someone point out what I'm missing or how to fix this so that Laravel correctly receives and validates the form data during an update with an image upload? Is there something inherently different in how Laravel handles PUT requests with multipart/form-data compared to POST requests, especially when images are involved?
Any help would be greatly appreciated!
Upvotes: 1
Views: 48
Reputation: 23
const { data, setData, put, processing, errors } = useForm({
nom: ville.nom || '',
region: ville.region || '',
region_code: ville.region_code || '',
city_code: ville.city_code || '',
geographie: ville.geographie || '',
histoire: ville.histoire || '',
image: null,
nombre_annonces_analysees: ville.nombre_annonces_analysees || 0,
...Object.fromEntries(DYNAMIC_FIELDS.map(field => [field.name, ville[field.name] || {}])),
});
const handleSubmit = (e) => {
e.preventDefault();
const formData = new FormData();
formData.append('_method', 'PUT');
Object.entries(data).forEach(([key, value]) => {
if (key === "image" && value instanceof File) {
formData.append(key, value);
} else if (typeof value === "object" && value !== null) {
formData.append(key, JSON.stringify(value));
} else if (value !== null && value !== undefined) {
formData.append(key, value);
}
});
router.post(route('villes.update', ville.id), formData, {
forceFormData: true,
});
};
public function update(Request $request, Ville $ville)
{
try {
Log::info('perform a update action 👇');
Log::info('Raw Request Content: ' . $request->getContent());
Log::info('Request data for updating Ville:', $request->all());
$villeData = $request->except('image', '_method');
foreach ($villeData as $key => $value) {
if (is_string($value) && is_array(json_decode($value, true)) && json_last_error() === JSON_ERROR_NONE) {
$villeData[$key] = json_decode($value, true);
}
}
Validator::make($villeData, [
'nom' => 'sometimes|required|string|max:255',
'region' => 'sometimes|nullable|string|max:255',
'nombre_habitants' => 'sometimes|nullable|array',
'tension_locative' => 'sometimes|nullable|array',
'tension_transactionnelle' => 'sometimes|nullable|array',
'geographie' => 'sometimes|nullable|string',
'histoire' => 'sometimes|nullable|string',
'tourisme_vie_etudiante' => 'sometimes|nullable|array',
'duree_moyenne_recherche_locataire' => 'sometimes|nullable|array',
'prix_moyen_logements' => 'sometimes|nullable|array',
'image' => 'nullable|image|mimes:jpeg,png,jpg,webp|max:2048',
'nombre_annonces_analysees' => 'sometimes|nullable|integer|min:0',
])->validate();
$ville->update($villeData);
if ($request->hasFile('image')) {
if ($ville->image) {
Storage::disk('public')->delete($ville->image);
}
$imagePath = $request->file('image')->store('images/villes', 'public');
$ville->update(['image' => $imagePath]);
}
return redirect()->back()->with('success', 'Ville mise à jour avec succès.');
} catch (ValidationException $e) {
return redirect()->back()->withErrors($e->errors())->withInput();
} catch (Exception $e) {
// Log::error('Error updating ville: ' . $e->getMessage());
return redirect()->back()->with('error', 'Une erreur inattendue est survenue.');
}
}
// villes routes
Route::prefix('villes')->name('villes.')->group(function () {
Route::get('/creer', [VilleController::class, 'create'])->name('create');
Route::post('/', [VilleController::class, 'store'])->name('store');
Route::get('/{ville}/modifier', [VilleController::class, 'edit'])->name('edit');
Route::get('/{ville}/copy', [VilleController::class, 'copy'])->name('copy');
Route::put('/{ville}', [VilleController::class, 'update'])->name('update');
Route::delete('/delete', [VilleController::class, 'destroy'])->name('destroy');
});
[2025-02-27 17:00:22] local.INFO: perform a update action 👇
[2025-02-27 17:00:22] local.INFO: Raw Request Content:
[2025-02-27 17:00:22] local.INFO: Request data for updating Ville: {"_method":"PUT","nom":"Amagne","region":"Grand Est","region_code":"44","city_code":"08008","geographie":"test","histoire":"test","nombre_annonces_analysees":"6","nombre_habitants":"{\"2025\":2,\"2026\":2,\"2027\":2,\"2028\":2,\"2029\":2,\"2030\":1,\"2031\":1,\"2032\":1,\"2033\":1,\"2034\":1,\"2035\":1}","tension_locative":"{\"2025\":3,\"2026\":4,\"2027\":4,\"2028\":4,\"2029\":4,\"2030\":5,\"2031\":5,\"2032\":5,\"2033\":5,\"2034\":4,\"2035\":3}","tension_transactionnelle":"{\"2025\":5,\"2026\":4,\"2027\":4,\"2028\":4,\"2031\":5,\"2032\":3}","tourisme_vie_etudiante":"{\"2025\":4,\"2026\":4,\"2027\":4,\"2028\":5,\"2029\":5,\"2030\":4}","duree_moyenne_recherche_locataire":"{\"2025\":4,\"2029\":5}","prix_moyen_logements":"{\"2025\":4,\"2028\":5}","image":{"Illuminate\\Http\\UploadedFile":"C:\\wamp64\\tmp\\php3929.tmp"}}
Upvotes: 0