DataTables.net DOM overridden with Phoenix LiveView DOM render

Title

How to Integrate Phoenix LiveView with DataTables.js Without DOM Conflicts?

Versions

  • Phoenix: 1.5.7
  • LiveView: 0.15.7
  • DataTables.js: 1.11.4

Problem

I’m using Phoenix LiveView to render a DataTable (via live_component) within a static route. The LiveView module is rendered with live_render (:not_mounted_at_router) inside a static .eex template. On initial mount, the DataTable works as expected. However, when handling an event (e.g., filtering rows), LiveView updates the DOM, overwriting the div.datatables_wrapper, which causes DataTables.js to lose track of its state.

The relevant project structure is:

  • Static Route: Items
  • LiveView: ItemLive (rendered in .eex via live_render)
  • Component: DataTableComponent (rendered in ItemLive via live_component)
  • Event: handle_event("filter", params, socket) updates filtered_rows in the socket assigns

Goal

I want LiveView to handle dynamic updates (e.g., filtering) while keeping DataTables.js functional for its features (sorting, pagination, etc.), without DOM conflicts.

What I’ve Tried

  1. Using phx-ignore on the DataTable

    • Idea: Let DataTables manage its DOM and push data via push_event(socket, "render-datatable-payload", %{}).
    • Issue: Offloads rendering to the client, reducing LiveView’s benefits. I’d prefer a server-driven solution.
  2. Destroying/Reinitializing DataTables

    • Idea: Use push_event to notify the JS hook to destroy the DataTable before LiveView updates the DOM, then reinitialize it.
    • Issue: The updated hook runs after the DOM update, and the event arrives too late, causing timing issues. Performance also concerns me.
  3. Ditching DataTables for a Native Solution

    • Idea: Use a Phoenix-native table library or custom implementation.
    • Issue: DataTables.js is heavily used in this project, and replacing it risks breaking user-expected functionality. Not a preferred option unless necessary.
  4. Hybrid Approach

    • Idea: Let LiveView handle data updates and minimize client-side DOM manipulation.
    • Issue: I’m struggling to make this work smoothly and need guidance.

Questions

  • Has anyone successfully integrated LiveView with DataTables.js in a similar setup?
  • How can I prevent LiveView from overwriting the DataTable DOM while still leveraging both tools?
  • Are there known patterns or hooks to synchronize LiveView updates with DataTables reinitialization?
  • Would upgrading to a newer LiveView version (e.g., 1.x) resolve this? (Upgrades are possible with justification.)

Relevant Code

item_live.ex

defmodule FulfillmentCartWeb.ItemLive do
  use FulfillmentCartWeb, :live_view

  def mount(:not_mounted_at_router, _session, socket) do
    {:ok, assign(socket, rows: Items.list_items(), options: options())}
  end

  def render(assigns) do
    ~L"""
    <%= live_component @socket,
          MyAppWeb.Components.DataTable,
          id: "item-table",
          rows: @rows,
          options: @options %>
    """
  end

  defp options do
    %{
      "stateSave" => true,
      "info" => false,
      "pageLength" => 15,
      "dom" => "Bfrtip",
      "buttons" => [%{"extend" => "csv", "text" => "Download CSV"}]
    }
  end
end

data_table.js (LiveView Hook)

const $ = window.jQuery;

const DataTableInit = {
  mounted() {
    this.initDataTable();
  },
  updated() {
    // Issue: Runs after DOM update, too late to destroy/reinit DataTable
  },
  initDataTable() {
    const tableId = `#${this.el.id}`;
    const options = JSON.parse(this.el.dataset.options || '{}');
    $(tableId).DataTable({ ...options });
  }
};

export default DataTableInit;

app.js

let liveSocket = new LiveSocket("/live", Socket, {
  params: {_csrf_token: csrfToken},
  hooks: { DataTableInit }
});

Notes

  • The issue only occurs on updates, not initial mount.
  • I’ve also posted this exact same question on Stack Overflow, not sure where the most active “moderating of questions/requests” is in Phoenix and Elixir is, but I’m looking for the most outreach.

Any advice or examples would be greatly appreciated!

Need to use phx-update="ignore" Bindings — Phoenix LiveView v1.0.9