Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

stream_insert update_only aka stream_update #3573

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions assets/js/phoenix_live_view/dom_patch.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ export default class DOMPatch {
}
},
onBeforeNodeAdded: (el) => {
// don't add update_only nodes if they did not already exist
if(this.getStreamInsert(el)?.updateOnly && !this.streamComponentRestore[el.id]){
return false
}

DOM.maintainPrivateHooks(el, el, phxViewportTop, phxViewportBottom)
this.trackBefore("added", el)

Expand Down Expand Up @@ -278,8 +283,8 @@ export default class DOMPatch {

liveSocket.time("morphdom", () => {
this.streams.forEach(([ref, inserts, deleteIds, reset]) => {
inserts.forEach(([key, streamAt, limit]) => {
this.streamInserts[key] = {ref, streamAt, limit, reset}
inserts.forEach(([key, streamAt, limit, updateOnly]) => {
this.streamInserts[key] = {ref, streamAt, limit, reset, updateOnly}
})
if(reset !== undefined){
DOM.all(container, `[${PHX_STREAM_REF}="${ref}"]`, child => {
Expand Down
6 changes: 5 additions & 1 deletion lib/phoenix_live_view.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1843,6 +1843,9 @@ defmodule Phoenix.LiveView do
here as well in order to be enforced. See `stream/4` for more information on
limiting streams.

* `:update_only` - A boolean to only update the item in the stream. If the item does not
exist on the client, it will not be inserted. Defaults to `false`.

## Examples

Imagine you define a stream on mount with a single item:
Expand Down Expand Up @@ -1892,8 +1895,9 @@ defmodule Phoenix.LiveView do
def stream_insert(%Socket{} = socket, name, item, opts \\ []) do
at = Keyword.get(opts, :at, -1)
limit = Keyword.get(opts, :limit)
update_only = Keyword.get(opts, :update_only, false)

update_stream(socket, name, &LiveStream.insert_item(&1, item, at, limit))
update_stream(socket, name, &LiveStream.insert_item(&1, item, at, limit, update_only))
end

@doc """
Expand Down
4 changes: 3 additions & 1 deletion lib/phoenix_live_view/engine.ex
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ defmodule Phoenix.LiveView.Comprehension do

@doc false
def __annotate__(comprehension, %Phoenix.LiveView.LiveStream{} = stream) do
inserts = for {id, at, _item, limit} <- stream.inserts, do: [id, at, limit]
inserts =
for {id, at, _item, limit, update_only} <- stream.inserts, do: [id, at, limit, update_only]

data = [stream.ref, inserts, stream.deletes]

if stream.reset? do
Expand Down
8 changes: 4 additions & 4 deletions lib/phoenix_live_view/live_stream.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ defmodule Phoenix.LiveView.LiveStream do
# with manually calling stream_insert multiple times, as stream_insert prepends.
items_list =
for item <- items, reduce: [] do
items -> [{dom_id.(item), -1, item, opts[:limit]} | items]
items -> [{dom_id.(item), -1, item, opts[:limit], opts[:update_only]} | items]
end

%LiveStream{
Expand Down Expand Up @@ -64,10 +64,10 @@ defmodule Phoenix.LiveView.LiveStream do
%{stream | deletes: [dom_id | stream.deletes]}
end

def insert_item(%LiveStream{} = stream, item, at, limit) do
def insert_item(%LiveStream{} = stream, item, at, limit, update_only) do
item_id = stream.dom_id.(item)

%{stream | inserts: [{item_id, at, item, limit} | stream.inserts]}
%{stream | inserts: [{item_id, at, item, limit, update_only} | stream.inserts]}
end

defimpl Enumerable, for: LiveStream do
Expand Down Expand Up @@ -106,7 +106,7 @@ defmodule Phoenix.LiveView.LiveStream do
defp do_reduce(list, {:suspend, acc}, fun), do: {:suspended, acc, &do_reduce(list, &1, fun)}
defp do_reduce([], {:cont, acc}, _fun), do: {:done, acc}

defp do_reduce([{dom_id, _at, item, _limit} | tail], {:cont, acc}, fun) do
defp do_reduce([{dom_id, _at, item, _limit, _update_only} | tail], {:cont, acc}, fun) do
do_reduce(tail, fun.({dom_id, item}, acc), fun)
end

Expand Down
3 changes: 2 additions & 1 deletion lib/phoenix_live_view/test/dom.ex
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,8 @@ defmodule Phoenix.LiveViewTest.DOM do

streamInserts =
Enum.reduce(streams, %{}, fn %{ref: ref, inserts: inserts}, acc ->
Enum.reduce(inserts, acc, fn [id, stream_at, limit], acc ->
# TODO: support update_only in LiveViewTest
Enum.reduce(inserts, acc, fn [id, stream_at, limit, _update_only], acc ->
Map.put(acc, id, %{ref: ref, stream_at: stream_at, limit: limit})
end)
end)
Expand Down
12 changes: 10 additions & 2 deletions test/phoenix_live_view/live_stream_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,20 @@

test "default dom_id" do
stream = LiveStream.new(:users, 0, [%{id: 1}, %{id: 2}], [])
assert stream.inserts == [{"users-2", -1, %{id: 2}, nil}, {"users-1", -1, %{id: 1}, nil}]

assert stream.inserts == [
{"users-2", -1, %{id: 2}, nil, nil},
{"users-1", -1, %{id: 1}, nil, nil}
]
end

test "custom dom_id" do
stream = LiveStream.new(:users, 0, [%{name: "u1"}, %{name: "u2"}], dom_id: &"u-#{&1.name}")
assert stream.inserts == [{"u-u2", -1, %{name: "u2"}, nil}, {"u-u1", -1, %{name: "u1"}, nil}]

assert stream.inserts == [
{"u-u2", -1, %{name: "u2"}, nil, nil},
{"u-u1", -1, %{name: "u1"}, nil, nil}
]
end

test "default dom_id without struct or map with :id" do
Expand All @@ -29,7 +37,7 @@
end
end

test "inserts are deduplicated (last insert wins)" do

Check failure on line 40 in test/phoenix_live_view/live_stream_test.exs

View workflow job for this annotation

GitHub Actions / mix test (OTP maint-27 | Elixir main)

test inserts are deduplicated (last insert wins) (Phoenix.LiveView.LiveStreamTest)

Check failure on line 40 in test/phoenix_live_view/live_stream_test.exs

View workflow job for this annotation

GitHub Actions / mix test (OTP 26.2 | Elixir 1.16.3)

test inserts are deduplicated (last insert wins) (Phoenix.LiveView.LiveStreamTest)

Check failure on line 40 in test/phoenix_live_view/live_stream_test.exs

View workflow job for this annotation

GitHub Actions / mix test (OTP 24.3 | Elixir 1.14.5)

test inserts are deduplicated (last insert wins) (Phoenix.LiveView.LiveStreamTest)

Check failure on line 40 in test/phoenix_live_view/live_stream_test.exs

View workflow job for this annotation

GitHub Actions / mix test (OTP 25.3 | Elixir 1.15.4)

test inserts are deduplicated (last insert wins) (Phoenix.LiveView.LiveStreamTest)

Check failure on line 40 in test/phoenix_live_view/live_stream_test.exs

View workflow job for this annotation

GitHub Actions / mix test (OTP 24.3 | Elixir 1.14.5)

test inserts are deduplicated (last insert wins) (Phoenix.LiveView.LiveStreamTest)

Check failure on line 40 in test/phoenix_live_view/live_stream_test.exs

View workflow job for this annotation

GitHub Actions / mix test (OTP 27.2 | Elixir 1.18.1)

test inserts are deduplicated (last insert wins) (Phoenix.LiveView.LiveStreamTest)
assert stream = LiveStream.new(:users, 0, [%{id: 1}, %{id: 2}], [])
stream = LiveStream.insert_item(stream, %{id: 2, updated: true}, -1, nil)
stream = %{stream | consumable?: true}
Expand Down
Loading