Laravel Form State.

Arif Khan
3 min readApr 28, 2023

Re-usability has been always the buzz word in the coding community. Every software developer everyday think about it, at least one time in his/her daily work.

One of the most common scenarios is reusing forms. As we all know creating and updating/editing an entity have same form structure, so being a responsible web artisans we all try to avoid repeating ourselves by not creating two separate forms for two similar operations.

Here is an approach that I follow, when I found myself in such situation.

Let me take an example, suppose I have a Post model

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
use HasFactory;

/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'title',
'body',
'type',
];
}

I have some routes

<?php

Route::get('/posts', [PostController::class, 'index'])->name('posts');
Route::get('/posts/create', [PostController::class, 'create'])->name('posts.create');
Route::post('/posts', [PostController::class, 'store'])->name('post.store');
Route::get('/posts/{post}/edit', [PostController::class, 'edit'])->name('posts.edit');
Route::post('/posts/{post}/update', [PostController::class, 'update'])->name('posts.update');

And the controller, which is responsible for passing the form_state to the front end. Checkout the edit() method.

<?php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;

class PostController extends Controller
{
private array $post_types = [
'blog' => 'Blog',
'article' => 'Article',
'basic page' => 'Basic Page',
];

public function index()
{
return inertia('Posts/Index', [
'posts' => Post::all(),
]);
}

public function create()
{
return inertia('Posts/Create', [
'types' => $this->post_types,
'form_state' => [],
]);
}

public function store()
{
$post = new Post;
$post->fill(request()->validate([
'title' => ['required', 'max:255'],
'body' => ['required', 'max:65535'],
'type' => ['required', Rule::in(array_keys($this->post_types))]
]))->save();

return back(303);
}

public function edit(Post $post)
{
return inertia('Posts/Edit', [
'types' => $this->post_types,
'form_state' => $post->only([
'id',
'title',
'body',
'type'
]),
]);
}

public function update(Post $post) {
$post->fill(request()->validate([
'title' => ['required', 'max:255'],
'body' => ['required', 'max:65535'],
'type' => ['required', Rule::in(array_keys($this->post_types))]
]))->save();

return back(303);
}
}

At the front end It has following code for creating and updating a post, notice in these files I am accepting the form_state from the backend passing it over to the Form.vue

Create.vue

<script setup lang="ts">
import GuestLayout from '@/Layouts/GuestLayout.vue';
import { Head } from '@inertiajs/vue3';
import Form from "@/Pages/Posts/Form.vue";
import {PostFormState} from "@/Pages/Posts/interface";

defineProps<{
types: Object,
form_state: PostFormState
}>();

</script>

<template>
<GuestLayout>
<Head title="Log in" />

<Form :types="types" :form_state="form_state"/>

</GuestLayout>
</template>

Edit.vue

<script setup lang="ts">
import GuestLayout from '@/Layouts/GuestLayout.vue';
import { Head } from '@inertiajs/vue3';
import Form from "@/Pages/Posts/Form.vue";
import {PostFormState} from "@/Pages/Posts/interface";

defineProps<{
types: Object;
form_state: PostFormState,
}>();

</script>

<template>
<GuestLayout>
<Head title="Log in" />

<Form :types="types" :form_state="form_state" />

</GuestLayout>
</template>

Now comes the Form.vue which is common for both edit and create operations.

<script setup lang="ts">
import InputError from '@/Components/InputError.vue';
import InputLabel from '@/Components/InputLabel.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import TextInput from '@/Components/TextInput.vue';
import {router, useForm} from '@inertiajs/vue3';
import {PostFormState} from "@/Pages/Posts/interface";

const props = defineProps<{
types: Object,
form_state: PostFormState,
}>();

const form = useForm(Object.assign({
title: '',
body: '',
type: 'blog',
}, props.form_state));

const submit = () => {
let path = props.form_state.id == null ? route('post.store') : route('posts.update', {post: props.form_state.id})
form.post(path, {
onFinish: () => {
form.reset()
router.visit(route('posts'))
},
});
};
</script>

<template>

<form @submit.prevent="submit">
<div>
<InputLabel for="title" value="Title"/>

<TextInput
id="title"
type="text"
class="mt-1 block w-full"
v-model="form.title"
required
/>

<InputError class="mt-2" :message="form.errors.title"/>
</div>

<div class="mt-4">
<InputLabel for="body" value="Body"/>

<textarea
id="body"
type="body"
class="mt-1 block w-full"
v-model="form.body"
required></textarea>

<InputError class="mt-2" :message="form.errors.body"/>
</div>

<div class="mt-4">
<InputLabel for="type" value="Type"/>

<select v-model="form.type">
<option v-for="(type,id) in types" :value="id" :key="id">{{ type }}</option>
</select>

<InputError class="mt-2" :message="form.errors.type"/>
</div>

<div class="flex items-center justify-end mt-4">
<PrimaryButton
class="ml-4"
:class="{ 'opacity-25': form.processing }"
:disabled="form.processing"
v-text="form_state?.id ? 'Update' : 'Create'"
>
</PrimaryButton>
</div>
</form>
</template>

here in this code I have defined the prop form_state and based on that decided weather the form is for creation or editing a post.

const form = useForm(Object.assign({
title: '',
body: '',
type: 'blog',
}, props.form_state));

this code is mainly responsible for maintaining the form state. It is initializing the form with the form_state coming from the backend and making values default.

I hope you enjoyed the article and learned something new today.

Thank you!

Video on the same topic:

Handling Forms in Laravel

--

--

Arif Khan

My specialties include quickly learning new skills and programming languages, problem-solving. Besides, I love circuit training 🤸 and weight lifting 🏋️