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.