How do I prevent my streams from getting reset when this modal's form successfully submits?

Hello! I am learning Phoenix and am trying to get a modal working. I started based on the one generated with mix phx.gen.live, and made some modifications so that it does not navigate to a new page (I want to add new instances of a resource from the home page) but I’ve gotten a bit tangled up between that and streams.
I’ve got this directory structure (with other paths elided)

.
├── gift_live
│   ├── form_component.ex
│   ├── form_component.html.heex
│   ├── index.ex
│   └── index.html.heex
├── home_live
│   ├── index.ex
│   └── index.html.heex

My planned flow is for a modal in home_live/index to call out to gift_live/form_component to handle the creation of a gift. The generator’s modal navigates to a new page (/gifts), but I wanted the page underneath the home-page modal to stay the same. However, the logic for showing and hiding the modal is (if I’m understanding correctly) based on page navigation; router sets the live_action that the modal uses.
I made this change:

<div class="flex justify-end gap-2">
  <.button phx-click="new-gift">New gift</.button>
</div>

...

<.modal
  :if={@live_action && @live_action in [:new_gift]}
  id="giftee-modal"
  show
  on_cancel={JS.patch(~p"/")}
>
...
<./modal>

With these functions handling new-gift (to open the modal) and successful gift creation in home_live/index.ex:

  @impl true
  def handle_event("new-gift", _unsigned_params, socket) do
    {:noreply,
     socket
     |> assign(:live_action, :new_gift)}
 
  @impl true
  def handle_info(
        {GiftTrackerWeb.GiftLive.FormComponent, {:saved, gift}},
        socket
      ) do
    {:noreply, stream_insert(socket, :gifts, gift)}
  end

This works, but my existing streams are emptied (that is, the page shows only the new gift, not any of the existing ones) when the modal fades out after either a successful submission or from exiting the modal.

Here’s how I’m setting up my streams (again in home_live/index.ex):

  def assign_user_resources(socket) do
    user = socket.assigns.current_user |> Repo.preload(:giftees) |> Repo.preload(:gifts)

    socket
    |> assign(:giftees, user.giftees)
    |> stream(:gifts, user.gifts |> Enum.map(fn g -> g |> Repo.preload(:giftees) end))
    |> assign(:current_user, user)
  end

  @impl true
  def mount(_params, _session, socket) do
    {:ok, assign_user_resources(socket)}
  end

I tried calling assign_user_resources on socket in handle_params, but that led to duplicated resources.
One thing that worked for exiting the modal (but not on successful submission) was to not assign the user resources in mount and assign them when applying :index.
I suppose I could re-stream the user resources when new-gift and handle_info execute, but that is the same as just using assign, right?
I tried using client-side modal opening and closing as well. I was able to get it opening (using the generated show-modal function) but couldn’t work out how to close it on submission.

Is there a correct way of doing what I would like to accomplish or a best practice for dealing with the streams?

Thank you for reading.

I figured it out! I was having two separate problems.

First, my initial concern was due to having this button:

    <.link patch={~p"/gifts/new"}>
      <.button>New Gift</.button>
    </.link>

on the root page of my application. This would work (it was generated, after all) but would change the page underneath to /gifts instead of staying at the home page. I was trying to open the modal without navigating to a new endpoint on HomeLive.Index. So, I did the following:

# in the router:
      live "/new-gift", HomeLive.Index, :new_gift

# in the home_live/index.ex:
  defp apply_action(socket, :new_gift, _params) do
    socket
    |> assign(:page_title, "New Gift")
  end

That gets me navigating correctly, correct stream behavior and stuff. But I had this separate issue about the streams “resetting” (not sure the correct terminology – the page would lose all the streamed values when the modal submitted.)
Turns out I was holding streams wrong. I moved the stream assignment logic back into mount/3 in home_live/index.ex:

  @impl true
  def mount(_params, _session, socket) do
    user =
      socket.assigns.current_user
      |> Repo.preload(:giftees)
      |> Repo.preload(:gifts)

    socket =
      socket
      |> assign(:giftees, user.giftees)
      |> stream(
        :gifts,
        user.gifts |> Enum.map(fn g -> g |> Repo.preload(:giftees) end)
      )
      |> assign(:current_user, user)

    {:ok, socket}
  end

The missing piece was adding phx-update="stream" to UI elements that depend on streams. I noticed I had missed this the first couple times I looked at this streams blog post. I think I missed it because it’s not in the diff and is instead a paragraph or two down. The perils of skimming! Anyways, everything seems to work now. Here’s how I added the phx-update attribute:

<ul phx-update="stream" id="gifts-for-me">
  <%= for {id, gift} when gift.for_me <- @streams.gifts  do %>
    <li id={id}>
      <%= gift.name %>
    </li>
  <% end %>
</ul>

<%= for giftee <- @giftees do %>
  <.header class="flex justify-between my-2">
    Gifts for <%= giftee.name %>
  </.header>
  <ul id="gifts-for-{giftee.name}" phx-update="stream">
    <%= for {id, gift} <- @streams.gifts  do %>
      <%= if giftee in gift.giftees do %>
        <li id={"gift-#{id}-for-#{giftee.id}"}>
          <%= gift.name %>
        </li>
      <% end %>
    <% end %>
  </ul>
<% end %>