Passing attributes to custom Macro containing heex fails during compile time

I have a Table Component, defined as a macro.

defmodule LiveTable.TableComponent do
    defmacro __using__(_opts) do
      quote do
        use Phoenix.Component
        import LiveTable.SortHelpers
     
        def live_table(assigns) do
          ~H"""
          # Heex template
          """
        end
      end
end

In my live_resource, I want to say
use LiveTable.TableComponent, table_options: table_options
and use the table_options in the heex template to render the table.

Can I access the table_options with unquote(opts[:table_options]) in the heex template? I dont need change tracking for it.

When I try to compile, i get this error-

== Compilation error in file lib/demo_web/live/join_live.ex ==
** (RuntimeError) ~H requires a variable named "assigns" to exist and be set to a map
(phoenix_live_view 1.0.5) expanding macro: Phoenix.Component.sigil_H/2
lib/demo_web/live/join_live.ex:3: DemoWeb.JoinLive.live_table/1

Why is this happening?
Is there a better way to do this while importing TableComponent into my live_resource, while passing it the table_options - which are defined in the live_resource?

You need to use var!:

def live_table(var!(assigns)) do
1 Like

Thanks!
That fixed it.

When I try to pass the table_options like this-

# live_resource.ex
use LiveTable.TableComponent, table_options: get_table_options()

 def deep_merge(left, right) do
        Map.merge(left, right, fn
          _, %{} = left, %{} = right -> deep_merge(left, right)
          _, _left, right -> right
        end)
      end

      def get_table_options() do
        app_defaults = Application.get_env(:live_table, :defaults, %{})

        unquote(Macro.escape(@default_options))
        |> deep_merge(app_defaults)
        |> deep_merge(table_options())
      end

I get an undefined function error.
error: undefined function get_table_options/0 (there is no such import)
(live_resource is another macro which is used in my live_view.

Why is this happening?

use is a compile time construct so its arguments are evaluated during compilation. You are passing it a function that is defined within the same module which is not possible since the module is not compiled yet. Moving it to another module will work. Careful, though, as this can create annoying compile time dependencies. Easiest advice there is not to move it to a module that is used by many other modules.

1 Like

Then whats the workaround for this? I need to pass the output of that function call to the TableComponent.
Move just those 2 functions into another module and import it into live_resource?

Or would you recommend another way to do this?

Yes, move to another module then either import or call fully qualified. I don’t fully get your example, though, as it’s missing some context. Where is @default_options coming from? What’s the difference between that and app_options? My thought it that you would do better to make your own __using__ implementation that does the default stuff behind the scenes that you can call like this:

use MyTableComponent, table_options: %{
  some: :option
}

Raised a separate issue since the problem for this topic was resolved.

table_options are passed from live_resource to Table component and helpers