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
vialive_render
) - Component:
DataTableComponent
(rendered inItemLive
vialive_component
) - Event:
handle_event("filter", params, socket)
updatesfiltered_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
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.
- Idea: Let DataTables manage its DOM and push data via
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.
- Idea: Use
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.
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!
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
vialive_render
) - Component:
DataTableComponent
(rendered inItemLive
vialive_component
) - Event:
handle_event("filter", params, socket)
updatesfiltered_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
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.
- Idea: Let DataTables manage its DOM and push data via
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.
- Idea: Use
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.
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!
Share Improve this question edited Apr 1 at 13:02 Thomas asked Mar 31 at 23:47 ThomasThomas 2,9821 gold badge13 silver badges19 bronze badges1 Answer
Reset to default 0I 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