In this post, I'd like to explain my process of handling & validating multiple forms that can have same-named fields. For example, you can have two side by side forms, say, login and signup on the same page. And you are likely to have matching fields like username, password, and such. If you use something like old('username')
as the value, the respective fields of both forms would be populated even if you submitted just one.
The Product
Disclaimer: This is experimental at the moment, I'm still refining my procedure and looking into other ideas. This is a journal of what I have come up with so far.
Alright, let's get down to it.
The Flow
- I add a hidden field called
_name
to my forms. Which holds an identifier for the form e.glogin
,signup
, etc. - On the controller side there is no change, yet. The validation logic stays the same.
- Before utilizing the old value of, say, username field. I check if the old value of
_name
is the same as defined earlier.
The Setup
As you might guess this adds some repetition to the forms and I like to keep things DRY. For this, we do have components to the rescue. Note, how flexible you make them is up to you.
<?PHP
// app/View/Components/Input.php
namespace App\View\Components;
use Illuminate\View\Component;
class Input extends Component
{
public $name;
public $id;
public $type;
public $label;
public $form;
/**
* Create a new component instance.
*
* @return void
*/
public function __construct($name= null, $id = null, $type=null, $label='text', $form = null)
{
//
$this->name= $name;
$this->id= $id ? $id : ($form && $name ? $form . '-' . $name : null);
$this->type= $type;
$this->label= $label;
$this->form= $form;
}
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\Contracts\View\View|string
*/
public function render()
{
return view('components.input');
}
}
Wouldn't it be good to have some helpers to perform the checks? For this, I set up custom blade directives that handle checking the conditionals.
<?php
// app/Providers/AppServiceProvider.php
namespace App\Providers;
use Illuminate\Support\ViewErrorBag;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
//
}
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Blade::if('from', function ($form = null) {
return old('_name') === $form;
});
Blade::if('invalid', function ($name, $form = null) {
$errors = session()->get('errors', app(ViewErrorBag::class));
return old('_name') === $form && $errors->has($name);
});
}
}
Here's how my InputComponent view looks like:
<div class="mb-3">
@if($label)
<label for="{{ $id }}" class="form-label">{{ $label }}</label>
@endif
<input type="{{ $type }}" class="form-control @invalid($name, $form) is-invalid @endinvalid" id="{{ $id }}"
name="{{ $name }}" value="@from($form){{ old($name) }}@endfrom($form)" />
@invalid($name, $form)
<div class="invalid-feedback">
{{ $errors->first($name) }}
</div>
@endinvalid
</div>
I also utilize a Form component to handle adding the hidden input field as well as automate handling the method for it.
Summary
This is just one of perhaps many more solutions out there. This works for me at the moment and I will still continue to explore more.
If you have any suggestions feel free to discuss them down below. Thank you for reading.
Oh and by the way, you can check out the project on my Github: https://github.com/zaxwebs/ex-l8-multi-forms-2
Top comments (2)
What about validateWithBag method in controller?
I beilieve validateWithBag doesn't solve this old('username') issue.