0

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-update="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.

Any advice or examples would be greatly appreciated!

    2 Answers 2

    1

    Using phx-update="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.

    When LiveView updates the DOM, Datatable still needs to do some work to reflect the DOM changes. Datatable is managing its own DOM, so LiveView should not interfere with it. When you say phx-update="ignore" you are telling LiveView not to update the DOM because you are going to do it yourself in your hook, on the client side.

    The only flow that I can think of that would work is to push the new data using push_event and handle it on the client side.

    // datatable_hook.js const DataTableInit = { mounted() { this.initDataTable(); this.handleEvent("datatable-update", ({ rows }) => { this.dataTable.clear(); this.dataTable.rows.add(rows); this.dataTable.draw(false); }); }, initDataTable() { const tableId = `#${this.el.id}`; const options = JSON.parse(this.el.dataset.options || '{}'); this.dataTable = $(tableId).DataTable({ data: [], ...options }); } }; 
    //my_live_view.ex def handle_event("filter-data", _params, socket) do {:noreply, push_event(socket, "datatable-update", %{rows: get_table_rows(/* whatever_filter_you_like */)})} end 
    1
    • This is what I'm doing now, and it's working. The advent of more reactive server side solutions has put me in a bad spot with DataTables. I'll resolve the question when I get the work finished and there is nothing more to add to this. Thanks!
      – Thomas
      CommentedApr 14 at 16:31
    0

    I don't think phx-ignore is a thing.

    However, what you need is phx-update="ignore". Put that on the root div of your nested LiveView.

    See here for more details: https://hexdocs.pm/phoenix_live_view/1.0.9/bindings.html#dom-patching

    1
    • My quarrel is with the concept of ignoring the back-end rendering when that's the entire purpose of LiveView. I'll have a look at client-side only solutions with more depth though.
      – Thomas
      CommentedApr 1 at 13:11

    Start asking to get answers

    Find the answer to your question by asking.

    Ask question

    Explore related questions

    See similar questions with these tags.