1

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!

3
  • I noticed that too. It's weird. I'm using POST when uploading images, that's my straightforward approach because I have deadlines and couldn’t dig deeper. I'll be following your post to see if anyone has a solution. Commented Feb 14 at 17:08
  • Check this Answer Commented Feb 17 at 15:12
  • @9uifranco look at the answer bellow I used ai and the code I posed below worked for me check if gonna work for u as well Commented Feb 27 at 17:24

1 Answer 1

0

I found the solution I ued this code and It worked


 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,
        });
    };

backend

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.');
        }
    }

Routes:

 // 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');
    });

this is how the log lloks like :

[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"}}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.