I'm pretty new to Laravel and am trying to learn the framework. I'm currently making a simple posts/leaderboard page where users should be able to upload an audio file to it. Everything works fine except for the audio upload.I'm not sure where the error is. I'm also using vite to use React code for my components.
For validation: I've tried using mimes such as audio/mpeg, mp3, mpeg, aac, wav. None of them worked when i uploaded an mp3. Neither does it save the file. Eventually I just tried making audio nullable, and adding no further validation. At that point it does redirect me to the posts page where I can see the data. But the audio is not saved anywhere. It is essentially lost.
I'll add all the necessary files below. Where am i doing something wrong?
create_inzending_table.php:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('inzendings', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->string('category');
$table->timestamp('posted_at')->nullable();
$table->string('upvotes')->nullable();
$table->string('description');
$table->string('posted_by');
$table->string('audio');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('inzendingen');
}
};
Requests/StoreInzending.php:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreInzending extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, mixed>
*/
public function rules()
{
return [
'audio' =>'nullable',
'title' => ['required', 'string', 'max:255'],
'description' => ['required', 'string', 'max:255'],
'posted_by' => ['required', 'string', 'max:255'],
'posted_at' => ['nullable', 'date'],
'category' => ['required', 'string', 'max:255'],
'upvotes' => ['integer'],
];
}
}
Models/Inzending.php:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Inzending extends Model
{
protected $fillable = [
'audio',
'title',
'description',
'imageUrl',
'posted_at',
'posted_by',
'category',
'upvotes'
];
use HasFactory;
}
routes/web.php:
Route::get('inzending', [InzendingController::class,'index'])->name('inzending.index');
Route::get('inzending/create', [InzendingController::class,'create'])->name('inzending.create');
Route::post('inzending', [InzendingController::class,'store'])->name('inzending.store');
CreateInzendingForm.jsx:
import React, { useState } from 'react';
import { Link, useForm, usePage } from '@inertiajs/react';
import InputError from '@/Components/InputError';
import InputLabel from '@/Components/InputLabel';
import PrimaryButton from '@/Components/PrimaryButton';
import TextInput from '@/Components/TextInput';
import SelectInput from '@/Components/SelectInput';
import FileInput from './FileInput';
export default function CreateInzendingForm({ mustVerifyEmail, status, className }) {
const user = usePage().props.auth.user;
const inzending = usePage()
const { data, setData, patch, post, errors, processing, recentlySuccessful } = useForm({
name: user.name,
email: user.email,
education: user.education,
cohort: user.cohort,
title: inzending.title,
description: inzending.description,
posted_at: inzending.posted_at,
category: inzending.category,
posted_by: user.name,
audio: inzending.audio,
});
const submit = (e) => {
e.preventDefault();
console.log(data);
post(route('inzending.store'));
}
return (
<section>
<header>
<h2 className="text-lg font-medium text-gray-900 dark:text-gray-100">Create Inzending</h2>
</header>
<form onSubmit={submit} encType='multipart/form-data' className="mt-6 space-y-6">
<div>
<InputLabel htmlFor="title" value="Title" />
<TextInput
id="title"
className="mt-1 block w-full"
onChange={(e) => setData('title', e.target.value)}
required
isFocused
autoComplete="title"
/>
<InputError className="mt-2" message={errors.title} />
</div>
<div>
<InputLabel htmlFor="description" value="Description" />
<TextInput
id="description"
className="mt-1 block w-full"
onChange={(e) => setData('description', e.target.value)}
required
isFocused
autoComplete="description"
/>
<InputError className="mt-2" message={errors.description} />
</div>
<div>
<InputLabel htmlFor="category" value="Category" />
<SelectInput
id="category"
className="mt-1 block w-full"
onChange={(e) => setData('category', e.target.value)}
required
isFocused
autoComplete="category"
/>
<InputError className="mt-2" message={errors.category} />
</div>
<div>
<InputLabel htmlFor="posted_by" value="Posted By" />
<TextInput
id="posted_by"
className="mt-1 block w-full"
value={data.name}
onChange={(e) => setData('posted_by', e.target.value)}
required
isFocused
autoComplete="posted_by"
/>
<InputError className="mt-2" message={errors.posted_by} />
</div>
<div>
<InputLabel htmlFor="audio" value="Audio" />
<FileInput
id="audio"
name="audio"
className="mt-1 block w-full"
onChange={(e) => setData('audio', e.target.value)}
required
isFocused
autoComplete="audio"
/>
<InputError className="mt-2" message={errors.audio} />
<div className="flex items-center gap-4">
<PrimaryButton disabled={processing}>Save</PrimaryButton>
</div>
</div>
</form>
</section>
);
}
FileInput.jsx:
import { forwardRef, useEffect, useRef } from 'react';
export default forwardRef(function FileInput({ type = 'file', name, className = '', isFocused = false, ...props }, ref) {
const input = ref ? ref : useRef();
useEffect(() => {
if (isFocused) {
input.current.focus();
}
}, []);
return (
<div className="flex flex-col items-start">
<input
{...props}
name={name}
type={type}
className={
'border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm ' +
className
}
ref={input}
/>
</div>
);
});
config/filesystems.php:
'links' => [
public_path('storage') => storage_path('app/public'),
public_path('images') => storage_path('app/public/images'),
public_path('videos') => storage_path('app/public/videos'),
public_path('audio') => storage_path('app/public/audio'),
],
Any help is greatly appreciated!
Answers
It seems like you've set up the file upload functionality correctly, but there might be a small issue in your React component handling the file input. Let's adjust the FileInput.jsx
component to ensure it handles file uploads properly:
import { forwardRef, useEffect, useRef } from 'react';
export default forwardRef(function FileInput({ name, className = '', isFocused = false, ...props }, ref) {
const inputRef = useRef();
useEffect(() => {
if (isFocused) {
inputRef.current.focus();
}
}, [isFocused]);
return (
<div className="flex flex-col items-start">
<input
{...props}
name={name}
type="file"
className={
'border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm ' +
className
}
ref={ref || inputRef} // Use ref passed as prop or the local ref
/>
</div>
);
});
In your CreateInzendingForm.jsx
, make sure you're setting up the FileInput
component correctly:
import FileInput from './FileInput';
// Inside the form
<FileInput
name="audio"
className="mt-1 block w-full"
onChange={(e) => setData('audio', e.target.files[0])} // Use e.target.files to get the file object
required
isFocused
autoComplete="audio"
/>
By making these adjustments, the file input should properly handle file uploads, and you should be able to save the audio file along with other form data when submitting the form. Make sure you're storing the file path or file name in your database audio
column, depending on how you're handling file storage in your Laravel application.