Hey there 👋, I recently ran into a situation where I needed to build a dynamic input field and thought I would share how I did it.
This how-to is going to cover how to generate an input field on demand, simply by clicking a link you'll be able to add an input field or remove the field. I'm also going to cover how to implement the validation for the dynamic inputs. So...lets get started.
I am going to assume that you already have your Laravel Livewire project setup.
Getting Started
The first thing you're going to want to do is create your Livewire component. You can do this by using the following command:
php artisan livewire:make DyanmicInputs
This will create your DynamicInputs class and your dynamic-inputs blade template.
Building the class
Now that we have our files generated, let's go to the DynamicInputs class and start building this out.
The first method and property we want to add are.
// DynamicInputs.php
public Collection $inputs;
public function addInput()
{
$this->inputs->push(['email' => '']);
}
// Taking advantage of Laravel Collections, we are simply
// pushing an array with a key of email and an empty string
// value to the inputs collection.
// This method will be called when we click the add input link.
The next method we want to add will remove the input from the inputs collection.
// DynamicInputs.php
public function removeInput($key)
{
$this->inputs->pull($key);
}
// Again, I'm using Laravel Collections here and I am using
// the pull method to remove the array with the specified key.
// This will be called when we click the remove input link.
Next we want to add the mount method so that we can load our initial field on load.
//DynamicInputs.php
public function mount()
{
$this->fill([
'inputs' => collect([['email' => '']]),
]);
}
// I am using the Livewire fill method to populate the inputs
// collection when the page loads. This is how we display our
// initial input field.
Creating the blade template
Now that we have most of the class done, let's dig into the blade template. I'm going to post the entire template below then we will go over whats going on.
<div class="max-w-xl w-full">
@foreach($inputs as $key => $input)
<div class="mt-12">
<div class="w-full">
<label for="input_{{$key}}_email" class="sr-only">Email</label>
<input type="email" id="input_{{$key}}_email" wire:model.defer="inputs.{{$key}}.email" class="shadow-sm border-0 focus:outline-none p-3 block w-full sm:text-sm border-gray-300 rounded-md" placeholder="you@example.com" autocomplete="off">
@error('inputs.'.$key.'.email') <span class="text-xs text-red-600">{{ $message }}</span> @enderror
</div>
@if($key > 0)
<div wire:click="removeInput({{$key}})" class="flex items-center justify-end text-red-600 text-sm w-full cursor-pointer">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd"></path></svg>
<p>Remove Input</p>
</div>
@endif
</div>
@endforeach
<div wire:click="addInput" class="flex items-center justify-center text-blue-600 text-sm py-4 w-full cursor-pointer">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z" clip-rule="evenodd"></path></svg>
<p class="ml-2">Add New Input</p>
</div>
<div class="w-full flex justify-end mt-12">
<button wire:click="submit" class="px-3 py-1 bg-blue-600 text-white rounded-lg">Submit</button>
</div>
</div>
// dynamic-inputs.blade.php
@foreach($inputs as $key => $input)
// This is a simple foreach loop that we use to iterate
// through the inputs collection. The $key is important!
// dynamic-inputs.blade.php
<label for="input_{{$key}}_email" class="sr-only">Email</label>
<input type="email" id="input_{{$key}}_email" wire:model.defer="inputs.{{$key}}.email" class="shadow-sm border-0 focus:outline-none p-3 block w-full sm:text-sm border-gray-300 rounded-md" placeholder="you@example.com" autocomplete="off">
// When creating dynamic input fields, make sure your for
// attribute and id attribute are dynamic. I accomplish
// this by using the $key value with those attributes.
// Also, for wire:model you want to use inputs.{{$key}}.email
// as your value here. Inputs being the name of the collection
// $key being the dynamic value, and email being the
// collection key.
// dynamic-inputs.blade.php
@if($key > 0)
<div wire:click="removeInput({{$key}})" class="flex items-center justify-end text-red-600 text-sm w-full cursor-pointer">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd"></path></svg>
<p>Remove Input</p>
</div>
@endif
// We want to hide the remove input link on the first field.
// To do this, simply add an if statement that checks to see
// if the $key is > 0. If it is, then show it else don't.
// dynamic-inputs.blade.php
<div wire:click="addInput" class="flex items-center justify-center text-blue-600 text-sm py-4 w-full cursor-pointer">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z" clip-rule="evenodd"></path></svg>
<p class="ml-2">Add New Input</p>
</div>
// And lastly we add the link to add the new input. Once
// You've add this you can go ahead and click it and you
// will be creating dynamic input fields.
Let's go over how to implement validation for this.
To implement validation into this, you'll need to add the following code snippets.
// DynamicInputs.php
protected $rules = [
'inputs.*.email' => 'required',
];
protected $messages = [
'inputs.*.email.required' => 'This email field is required.',
];
public function submit()
{
$this->validate();
}
// You'll notice we're using . notation to set our validation
// rules.
Now let's add the validation to the front end. Below your input you'll want to add the following code.
// dynamic-inputs.blade.php
@error('inputs.'.$key.'.email') <span class="text-xs text-red-600">{{ $message }}</span> @enderror
// Notice the $key variable, you have to set this dynamically
// to make sure you catch the correct error for the correct
// input.
I created a sandbox version of this here
I hope you enjoyed this article, feel free to comment below if you know of a way to improve this.
Top comments (11)
Thanks ! Brother. I noticed that when I click "
Remove Input
" and then try to add a new input again, the "Add New Option
" button no longer works. I solved this issue by changing the div tag from "Add new option" with a button tag.How Can fully Dynamic Validation in livewire Laravel
Controller
Blade file
Good stuff Jonathon. Thanks for sharing
Thanks! Anytime.
how to perform inline calculation i.e. in case of invoice qty*price to get inline total?
I'm actually building an invoicing feature that does just this. I have a collection called inputs and this is where all the input data lives. Then I do the following:
I used wire:change on blade
to call inlineTotal
thanks
Muy bueno! Good!
Very good but how could I dynamically enter that data in a save method?
Thanks Bro! but when i add js datepicker with inputs does not work for me