You could always use module attributes as constants, since if you typo and reference a non-existing one you get a warning. EDITOR’S NOTE: this does not work as writ, see fceruti’s response
defmodule HelloWeb.UserLive.FormComponent do
use HelloWeb, :live_component
@reset_password_event "reset_password"
# Use in the render:
def render(assigns) do
~H"""
<.simple_form
for={@form}
id="user-form"
phx-target={@myself}
phx-change={@reset_password_event}
phx-submit="save"
>
# ...
"""
end
# And in the handler:
def handle_event(@reset_password_event, %{"user" => user_params}, socket) do
# ...
end
end
If you have a lot of commonly used event names, you could put them into your root web live_component macro:
defmodule HelloWeb do
def live_component do
quote do
use Phoenix.LiveComponent
unquote(html_helpers())
@validate_event "validate"
@update_event "update"
@delete_event "delete"
# ... etc
end
end
end
Module attributes can’t be accessed inside HEEX since the @ operator is used by the engine to access socket.assigns.
You’d need to do:
defmodule HelloWeb.UserLive.FormComponent do
use HelloWeb, :live_component
@reset_password_event "reset_password"
# Use in the render:
def render(assigns) do
~H"""
<.simple_form
for={@form}
id="user-form"
phx-target={@myself}
phx-change={reset_password_event()}
phx-submit="save"
>
# ...
"""
end
# And in the handler:
def handle_event(@reset_password_event, %{"user" => user_params}, socket) do
# ...
end
defp reset_password_event(), do: @reset_password_event
end
I don’t think that actually works does it? Inside heex if I reference a module variable I get an error because it’s not in the assigns. So I’d have to copy the module variable into the assigns wherever I need it.
Another issue is module variables aren’t accessible outside of their module, so they won’t work for reusable components or live components. For those I can force the caller to pass in the name they want to use for the event, but that doesn’t feel great either.
Ah, of course it doesn’t, forgot to hit compile on my test project one last time
@fceruti 's solution both works with Heex assigns and addresses this problem!
I’ll add to it that when I expose a module attribute like this, I like to instruct the compiler to inline it, so it is as effective as if you’d placed the literal there in the first place:
defmodule HelloWeb.Events do
@reset_password_event "reset_password"
@compile {:inline, reset_password_event: 0}
def reset_password_event, do: @reset_password_event
end
Oh nice. I thought that a show.heex wouldn’t have access to that function in show.ex and would have to call WidgetShow.reset_password_event() instead, but it works perfectly.
Does the @reset_password_event attribute do anything here? I think the code will be the same if you put "reset_password" inside the .reset_password_event function
Also, I’m not sure inlining works across modules like you suggests, this is from the docs:
I think you’re absolutely right here–both in that I’ve misunderstood inlining, and that the difference is probably negligible regardless. Thanks for pointing this out to me–looks like the docs have made things clearer since I last read them and internalized this pattern!
That’s interesting. I find creating liveviews specifically to be very interactive - I have the running app that constantly refreshes and I iterate by clicking around the ui. So for me it doesn’t add much to have compiler errors as the app is already running if that makes sense.
That surprises me actually. I frequently have things used in multiple places where I can easily forget to update one on a refactor.
Eg a WidgetPicker that dispatches “widget_selected”. I refactor it to “widgets_selected” and don’t notice that I missed a place until the user clicks a button on a view I rarely use. It’s JS, so testing it requires more setup (like integration tests that actually drive the browser).
If that’s the case, how does the accepted answer solve it? If you change it in Elixir, you’re not going to get a warning in JS. Unless I’m missing something really obvious here.
I think testing is the right answer, here. Wallaby is quite fast, headless, and has very simple syntax.
I wrap a JS widget library in a live component. So the event is triggered by JS (requires Wallaby like you suggested), but ends up coming through from Elixir. I’ve also thought for the fully JS (hooks) I could pass in the name as a data param to keep it in Elixir <div phi-hook=“WidgetPicker” data-select-event={SomeView.widget_select_name()} />
I agree testing is the current best solution, but my code is highly volatile right now so I’m punting on testing until it’s more stable.
Ha, I try that too, but have also over-zealously replaced too many things in the past, when names overlap. I recently broke part of my app by bulk replacing mouseover with mouseenter. I forgot that there were instances of this event from JS components (not from the hook that I updated.)
I agree that I like to lean on the tooling. I guess I prefer the tooling to yell at me rather than relying on my memory to find/replace and not screw up in that process.
I’m feeling from this thread that I’m hitting some friction that nobody else in the community hits.