When I was writing an application using Stimulus and needed to use intl-tel-input, I came across this "Github issue".
In fact, to work, intl-tel-input clones the base element, so if you link this base element to a Stimulus controller, and it's this controller that instantiates intl-tel-input, you'll need to use intl-tel-input.If you link this base element to a Stimulus controller, and it's this controller that instantiates intl-tel-input, then you're simply entering an infinite loop until you completely exhaust the memory space allowed to you by the browser engine.
In the "Github issue", DHH says "If you create an infinite loop, it's hard to counter".
We'll simply "cheat", as usual, we know that the base element can't be used to trigger a Stimulus controller, but an element above it, it's possible isn't it?
The idea is very simple: we surround our "input" with a div, and link this div to the Stimulus controller. Then our "input" is simply a "target", and when intl-tel-input does what it's supposed to do, there's no need to recreate another Stimulus controller.
To do this in your template (in my case, a twig)
<div class="col-12 col-md-6"> | |
<div class="col-12"> | |
{{ form_label(form.tmpPhone) }} | |
</div> | |
<div class="col-12" {{ stimulus_controller('intl-tel-input' , { | |
'locale' : app.request.locale, | |
'currentNumber': reservation.phone | |
}) }}> | |
{{ form_widget(form.tmpPhone) }} | |
</div> | |
</div> |
Simply place the Stimulus controller on the div surrounding your input.
Then put an attribute on the "input", in my case in a Symfony form.
<?php | |
namespace App\Form; | |
class MyGreatForm extends AbstractType | |
{ | |
public function buildForm(FormBuilderInterface $builder, array $options): void | |
{ | |
$builder | |
->add('tmpPhone', TextType::class, [ | |
'required' => true, | |
'attr' => [ | |
'data-intl-tel-input-target' => 'input', | |
'autocomplete' => 'tel' | |
] | |
]); | |
} | |
} |
(Yes, I've added a little "autocomplete" too, isn't that nice?)
Finally, here's an example of my Stimulus controller
import {Controller} from '@hotwired/stimulus' | |
import intlTelInput from 'intl-tel-input'; | |
export default class extends Controller { | |
static targets = ['input']; | |
static values = { | |
currentNumber: String | |
}; | |
connect() { | |
let localizedCountries = {}; | |
this.intlInput = intlTelInput(this.inputTarget, { | |
utilsScript: '/static/intl-tel-input-utils.js', | |
hiddenInput: 'phone', | |
preferredCountries: ['fr', 'lu', 'be', 'de', 'ch'], | |
localizedCountries | |
}); | |
if (this.currentNumberValue !== '') { | |
this.intlInput.setNumber(this.currentNumberValue) | |
} | |
} | |
disconnect() { | |
this.intlInput.destroy(); | |
super.disconnect(); | |
} | |
} |
Nothing special, except that you never work with "this.element".
Seeing the "outcome", I thought it might be a good idea to do a quick article on this problem, so I did.
Until next time?