What tool to use to ensure every HTML element with a phx-hook has also an ID?

What tool (besides of a human brain) can be used to detect:

  • HTML elements with a phx-hook but with a missing ID
  • duplicate HTML IDs

I’m aware of the detection supported by the liveview client - which is very handy.
Screenshot 2025-04-21 162329

But also looking for something to be used during editing and perhaps in a CI pipeline

How are others handling it?

As far as CI goes, duplicate IDs also cause an Elixir warning, so using --warning-as-errors when compiling in CI will catch them.

Unfortunately not always if you have a fixed ID in a component that’s instantiated multiple times… I’d be interested in the answer!

Not sure if tools exist for this as most people likely just find out when the JS stops working or a warning shows up.

You could try something along the lines of:

JS

    const allIds = Array.from(document.querySelectorAll("[id]"));
    const duplicateIds = allIds
      .map(el => el.id)
      .filter((value, index, self) => self.indexOf(value) !== index);

    if (duplicateIds.length > 0) {
      hook.pushEvent("duplicate", { yourParams: yourParams });
    }

Server

  def handle_event("duplicate", _params, socket) do
    {:noreply, assign(socket, :duplicate, true)}
  end

Heex

  <% if @duplicate do %>
    DUPLICATE ID
  <% end %>

Haven’t tested the above, but the general plan is to set up teh JS to check the dom for all ID values, compare them against each other then trigger a server side event to toggle a flag that in turn conditionally renders a message/beacon of your choice.

You could also just set the event to trigger IO.puts as well.

Random add on answers because components solve all of lifes problems.

  attr :hook, :string, default: nil, examples: [target="yourHook"]

  slot :inner_block

  def hook(assigns) do
    ~H"""
      <div id={@hook} phx-hook={@hook}>
        {render_slot(@inner_block)}
      </div>
    """
  end
  attr :id, :string, default: Ecto.UUID.generate()

  slot :inner_block

  def component(assigns) do
    ~H"""
      <div id={@id}>
        {render_slot(@inner_block)}
      </div>
    """
  end
1 Like

I use the page pattern for testing, which makes it really easy to have shared test behavior. So when visiting a page, my shared page-visiting code can make those types of assertions.

Even if you don’t use the page pattern, you can probably find a way to check everything. Perhaps a custom render function for your tests that delegates to whatever render function you are already using, and then checks for duplicate IDs.

I make functions to generate all IDs and put all those functions in a dedicated module:

def flash_error_div, do: "flash-error-div"

def message(id), do: "message-#{id}"

Then, I always call those functions, never hard code IDs:

<div id={Ids.message(@message.id)}> ... </div>

It does not ensure anything, but it does make human error easier to spot.

2 Likes