skip to Main Content

for several days I have problem uploading an image with Laravel api. I was sending request via React’s modified axios. Code is as follows.

axios-client.js

import axios from "axios";

const axiosClient = axios.create({
    baseURL : `${import.meta.env.VITE_API_BASE_URL}/api`
});

axiosClient.interceptors.request.use((config) => {
    const token = localStorage.getItem('ACCESS_TOKEN');
    config.headers.Authorization = `Bearer ${token}`;

    if (config.data instanceof FormData) {
        console.log('Form Data accepted')
        config.headers['Content-Type'] = 'multipart/form-data';
    }

    return config;
});

axiosClient.interceptors.response.use((response) => {
    return response;
}, (error) => {
    try {
        const { response } = error;
        if (response.status === 401) {
            localStorage.removeItem('ACCESS_TOKEN');
        }
    } catch (e) {
        console.log(e);
    }
    throw error;
});

export default axiosClient;

React’s component

BrandForm.jsx

import React, { useState, useEffect } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import axiosClient from '../../axios-client';

function BrandForm() {
  const {id} = useParams();
  const navigate = useNavigate();
  const [loading,setLoading] = useState(false);
  const [errors, setErrors] = useState(null);
  const [brand,setBrand] = useState({
    id: null,
    name: '',
    logo: null
  });
  const config = {headers: {'Content-Type': 'multipart/form-data'}}

  if (id){
    useEffect(()=>{
        setLoading(true);
        axiosClient.get(`/car-make/${id}`)
            .then(({data})=>{
              console.log(data)
                setBrand(data);
                setLoading(false)
            })
            .catch((e)=>{
                console.log(e)
                setLoading(false);
            })
    },[]);
  }
  const onSubmit = (e)=>{
    e.preventDefault();
    setErrors(null);
    
    if (brand.id){
        axiosClient.put(`/car-make/${brand.id}`, brand, config)
        .then(({data})=>{
            //TODO show notification
            console.log('response update', data)
            navigate(path);
        })
        .catch( (err) => {
          console.log(err)
            const response = err.response;
            if(response && response.status ===422){
              setErrors(response.data.errors);
            }
          });
    } 
    else {
      
        axiosClient.post(`/car-make`, brand, config)
        .then(()=>{
            //TODO show notification
            console.log('response create', data)
            navigate(path);
        })
        .catch( (err) => {
            const response = err.response;
            if(response && response.status ===422){
              setErrors(response.data.errors);
            }
          });

    }

  }

  return (
    <div>
      {brand?.id ? <h1>Edit Brand: {brand.name}</h1> : <h1>New Brand</h1>}
      <div className="card animated fadeInDown">
        {loading && <div className="text-center">Loading...</div>}
        {errors && (
          <div className="alert">
            {Object.keys(errors).map((key) => (
              <p key={key}>{errors[key][0]}</p>
            ))}
          </div>
        )}
        {!loading && (
          <form onSubmit={onSubmit}>
            <input
              value={brand?.name}
              onChange={(e) => setBrand({ ...brand, name: e.target.value })}
              type="text"
              placeholder="Name"
            />
            <input
              onChange={(e) => setBrand({ ...brand, logo: e.target.files[0] })}
              type="file"
              placeholder="Logo"
            />
            <button className="btn" style={{ marginTop: '20px' }}>Save</button>
          </form>
        )}
      </div>
    </div>
  );
}

export default BrandForm;

Laravel’s

api.php

Route::middleware('auth:sanctum')->group(function(){

    Route::put('/transfer/execute/{id}', [TransferController::class,'execute']);
    Route::put('/transfer/cancel/{id}', [TransferController::class,'cancel']);
    Route::apiResource('/transfer', TransferController::class);
    Route::apiResource('/vehicle', VehicleController::class);
  
    Route::apiResource('/car-make', CarMakeController::class);
    Route::apiResource('/car-model', CarModelController::class);

    Route::get('/companies/partner',[CompanyController::class,'indexPartner']);
    Route::get('/companies/fleet',[CompanyController::class,'indexFlota']);
    Route::apiResource('/companies', CompanyController::class);
    
    
    Route::get('/user', function (Request $request) {
        return $request->user();
    });
    Route::post('/logout',  [AuthController::class,'logout']); 
    Route::apiResource('/users', UserController::class);

   
});


Route::post('/signup',  [AuthController::class,'signup']);
Route::post('/login',   [AuthController::class,'login']);

Model

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;

class CarMake extends Model
{
    use HasFactory;
    protected $fillable = ['name', 'logo'];
    protected $primarykey = "id";
    
  
    public function carModels()
    {
        return $this->hasMany(CarModel::class,'make');
    }
}

Controller

.
.
.
public function update(UpdateCarMakeRequest $request, CarMake $carMake)
    {
        Log::info('Update method called');
        Log::info('Update Brand Request - Raw Request Data:', $request->all());
        
        $data = $request->validated();
    
        if ($request->hasFile('logo')) {
            $file = $request->file('logo');
            $fileName = time() . '_' . $file->getClientOriginalName();
            $filePath = $file->storeAs('logo', $fileName, 'public');
            $data['logo'] = $filePath;
        } else {
            unset($data['logo']);
        }
    
        Log::info('Update Brand Request - Validated Data:', $data);
    
        $carMake->update($data);
    
        return new CarMakeResource($carMake);
    }
.
.
.

Update Request

class UpdateCarMakeRequest extends FormRequest
{
    public function authorize()
    {
        return true; // Adjust as needed
    }

    public function rules()
    {
        return [
            'name' => 'required|string|max:255',
            'logo' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
        ];
    }

    protected function failedValidation(Validator $validator)
    {
        Log::error('Validation errors:', $validator->errors()->toArray());
        throw new HttpResponseException(response()->json([
            'errors' => $validator->errors()
        ], 422));
    }
}

Reource

class CarMakeResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return [
            'id'=> $this->id,
            'name' => $this->name,
            'logo'=> $this->logo,
        ];

    }
}

The Log file looks like this after trying to edit

[2024-07-18 05:29:50] local.ERROR: Validation errors: {"name":["The name field is required."]} 
[2024-07-18 05:44:36] local.ERROR: Validation errors: {"name":["The name field is required."]} 

Note: when I try updating CarMake without an image and without config parameter in axios
request it works fine

Thanks in advance for your effort!

2

Answers


  1. Your request interceptor data handling is completely redundant. Axios already handles FormData payloads and does so correctly. Yours is missing the mime boundary token.

    Remove this and let Axios and your browser set the appropriate content-type header

    if (config.data instanceof FormData) {
      console.log('Form Data accepted')
      config.headers['Content-Type'] = 'multipart/form-data';
    }
    

    You also cannot embed files in a JSON document. Create a FormData instance for your request payload, populate the data and send that

    const onSubmit = async (e) => {
      e.preventDefault();
      setErrors(null);
    
      const fd = new FormData();
      Object.entries(brand).forEach(([key, value]) => {
        if (value !== null) fd.append(key, value);
      });
    
      try {
        if (brand.id) {
          await axiosClient.put(`/car-make/${encodeURIComponent(brand.id)}`, fd);
        } else {
          await axiosClient.post(`/car-make`, fd);
        }
        
        // use your dev-tools to inspect responses, not console.log()
    
        navigate(path);
      } catch (err) {
        console.warn(err);
        const response = err.response;
        if (response && response.status === 422) {
          setErrors(response.data.errors);
        }
      }
    };
    

    For Laravel to handle multipart/form-data PUT requests, you might need to resort to axios.post() and append _method=PUT

    await axios.post(`/car-make/${encodeURIComponent(brand.id)}`, fd, {
      params: {
        _method: 'PUT',
      },
    });
    

    See PATCH and PUT Request Does not Working with form-data

    Login or Signup to reply.
  2. Method validated() use for retrieving validated input.

    Within your controller, instead of validated() method, use $request->validate()

    So, your controller should be look likes

    public function update(UpdateCarMakeRequest $request, CarMake $carMake)
        {
            Log::info('Update method called');
            Log::info('Update Brand Request - Raw Request Data:', $request->all());
            
            $data = $request->validate();
        
            if ($request->hasFile('logo')) {
                $file = $request->file('logo');
                $fileName = time() . '_' . $file->getClientOriginalName();
                $filePath = $file->storeAs('logo', $fileName, 'public');
                $data['logo'] = $filePath;
            } else {
                unset($data['logo']);
            }
        
            Log::info('Update Brand Request - Validated Data:', $data);
        
            $carMake->update($data);
        
            return new CarMakeResource($carMake);
        }
    

    You can refer to docs for more information

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search