live_toast icon indicating copy to clipboard operation
live_toast copied to clipboard

Is there any better way to pass assigns to the :action option?

Open dfalling opened this issue 1 year ago • 7 comments

Hi, first off thank you so much for this lib! I was especially attracted to it for built-in support for notification actions, which I was putting off implementing.

Unless I'm missing something, it's currently a bit of a chore to get data into an action button:

  @impl true
  def handle_info(...) do
    url = ~p"/emails/#{email}"
    action = create_link_button(url)

    LiveToast.send_toast(
      :info,
      "Email \"#{email.subject}\" was processed successfully",
      action: action
    )

    {:noreply, socket}
  end

  # have to wrap the :actions function with another function to pass state in
  defp create_link_button(url) do
    fn assigns ->
      assigns = assign(assigns, :url, url)
      ~H"""
      <.button phx-click={JS.patch(@url)} type="button" style={:secondary}>View</.button>
      """
    end
  end

Is there a better way to do this? Thanks!

dfalling avatar May 30 '24 16:05 dfalling

Thank you, let me think of a good API change here to make this a little easier.

srcrip avatar Jun 06 '24 01:06 srcrip

I'm down for API changes if you have a suggestion, but does something like this pattern better solve it?

  opts = [
    action: fn assigns ->
      assigns
      |> Map.put(:id, "123")
      |> download_action()
    end
  ]
  
  LiveToast.send_toast(:info, "File parsed successfully.", opts)
  attr :id, :string, required: true

  def download_action(assigns) do
    ~H"""
    <button
      class="my-4 mr-4 text-sm font-medium bg-zinc-900 text-zinc-100 px-2 py-1 rounded-md hover:bg-zinc-800 hover:text-zinc-200"
      phx-click="download"
      phx-value-id={@id}
    >
      Download Item <%= @id %>
    </button>
    """
  end

If I had to resuse that more than once I'd probably also make a helper function like:

def send_download_toast(download_id) do
  opts = [
    action: fn assigns ->
      assigns
      |> Map.put(:id, "123")
      |> download_action()
    end
  ]

  LiveToast.send_toast(:info, "File parsed successfully.", opts)
end

and reuse it if it was needed.

That being said I am down to change the API for this customization if you have a suggestion. It's just kind of a hard problem. I think passing a function is the write move though because you can do stuff like this where you just edit the assigns.

srcrip avatar Jun 06 '24 14:06 srcrip

Also if this pattern is satisfying, we can put it in the docs.

srcrip avatar Jun 06 '24 14:06 srcrip

Yeah, I agree that those examples would be perfect- it took me a minute to grok my current solution and that looks cleaner.

One other idea- would it be too heavy or messy for send_toast's options to have an assigns that's merged in and passed to any action or custom notification component?

Eg.

def send_download_toast(download_id) do
  opts = [
    action: &link_button/1,
    assigns: [url: ~p"/files/avatar.png", icon: "hero-download"]
  ]

  LiveToast.send_toast(:info, "File parsed successfully.", opts)
end

My only issue with the current solutions is that wrapping the action / custom component in a lambda isn't as intuitive for me. But I could definitely be over-complicating this.

If this feels too complicated, I think the alternative is just for me to make my own helper in my codebase that takes assigns and action and returns a new action.

dfalling avatar Jun 06 '24 18:06 dfalling

I think passing the assigns would not be a great default, but I could be persuaded otherwise if there was something you couldn't accomplish by building the lambda. I understand it may be a little counterintuitive though.

srcrip avatar Jul 06 '24 12:07 srcrip

That's fair, the workarounds are fine. I can take a stab at a PR to add docs for them if you'd like.

dfalling avatar Jul 07 '24 09:07 dfalling

Sure, it would be appreciated!

srcrip avatar Jul 09 '24 17:07 srcrip

Hello @dfalling, sorry we didn't merge your PR earlier, I've had a lot of other things I've been working on. I'm still interested in getting an example along the lines of what you have here but I'm not sure exactly what it should look like. It is kind of an elixir question more than one concerning this library exactly.

I'm not sure myself if it really needs its own section, or more so just some kind of explanation at the places where we detail the various options what kind of parameters the action callback takes, you know what I mean?

srcrip avatar Dec 31 '24 17:12 srcrip

Hey @srcrip, no worries! I had originally asked because while it's very Elixir-y to make a closure to capture some data, it wasn't my first thought for a callback from a notification. I thought I'd pass some data to the notification which would be passed on to the component in the assigns.

Definitely not a problem now that I know, but it felt like something that might trip up other people as well.

dfalling avatar Jan 02 '25 12:01 dfalling

Thank you for the clarification!

srcrip avatar Jan 02 '25 21:01 srcrip