hotwired / stimulus

A modest JavaScript framework for the HTML you already have
https://stimulus.hotwired.dev/
MIT License
12.73k stars 426 forks source link

Browser/Tab Crash - intl-tel-input library with StimulusJS #411

Closed dylandamsma closed 3 years ago

dylandamsma commented 3 years ago

Hi there, my first ever issue on github, so forgive me if I lack the right etiquette.


Rails version: 6.1.0 StimulusJS version: ^2.0.0 Hotwire-rails: 0.1.2


Issue: When trying to implement intl-tel-input library through a stimulusJS controller, the browser/tab crashes (CPU ramps up to 100% and process has to be killed). The exact same code works as vanillaJS in the same rails app, loading the same library. Reviewed with another developer, and couldn't find issues in my code specifically.

Library: https://github.com/jackocnr/intl-tel-input

Code

VanillaJS ✅ working (Implemented as a JS file under /app/javascript/src/intl_tel_input.js)

import intlTelInput from 'intl-tel-input';

document.addEventListener("turbo:load", () => {
    if (document.querySelector(".phone-input-format")) {
        const input = document.querySelector(".phone-input-format");
        intlTelInput(input, {
            utilsScript: "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.13/js/utils.min.js",
            hiddenInput: "phone",
            initialCountry: "my",
            preferredCountries: ["my", "sg", "id"],
            separateDialCode: true,
            autoPlaceholder: "aggressive"
        });
    }
});

StimulusJS controller ❌ broken

import { Controller } from 'stimulus';
import intlTelInput from 'intl-tel-input';

export default class extends Controller {
    static targets = [ "input" ]

    connect() {
        const input = this.inputTarget;
        intlTelInput(input, {
            utilsScript: "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.13/js/utils.min.js",
            hiddenInput: "phone",
            initialCountry: "my",
            preferredCountries: ["my", "sg", "id"],
            separateDialCode: true,
            autoPlaceholder: "aggressive"
        });
    }
  }

target/input field

<%= f.text_field :phone, id: "phone", autocomplete: "phone", class: "form-control", data: { controller: "phoneinput", 'phoneinput-target': 'input' } %>

Please let me know if any additional information is required. I got it working as vanillaJS code for now, but this may help you guys identify an issue.

Thanks!

adrienpoly commented 3 years ago

I suspect intl-tel-input is creating a copy of the field. Since this field has the controller attached to it, it reconnects within the copy endlessly.

I would try something like that

<div data-controller="phoneinput">
    <%= f.text_field :phone, id: "phone", autocomplete: "phone", class: "form-control", data: { 'phoneinput-target': 'input' } %>
</div>
dylandamsma commented 3 years ago

Right... @adrienpoly

Curses while swinging fist in the air

You're correct, that tiny change fixed it. So it was my mistake after all. Thanks for the quick comment!

dylandamsma commented 3 years ago

@adrienpoly A fellow developer from the rails community has isolated the following bit which causes the crash in Safari, Chrome & Firefox. This might be useful for better error handling on this particular type of crash/conflict.

https://gist.github.com/dmrty/0a60b147e8c4e395ab679e655c460aab

Thanks to @dmrty

sudhirj commented 3 years ago

Are there any thoughts on how / if it's possible to fix something like this? This is pretty much the equivalent of an infinite loop or fork bomb, not sure how to address it.

dhh commented 3 years ago

You’re explicitly creating a recursive loop that’ll add dom elements forever? Don’t see how we can guard against that.