How do you organize your components with Phoenix 1.7?

It’s pretty much to avoid mentioning my “WhateverWeb” module name and making it universal for any user to simply copy-paste into their module between do and end. It’s taking a current module name, splitting it so that you can get a parent module name and then concatenating it with a CoreComponents atom.

1 Like

How do you add it to html_helpers/0 ? I am trying to make it work by adding it as:

use MyAppWeb.Components

but then I get a

== Compilation error in file lib/my_app_web/controllers/page_html.ex ==
** (CompileError) lib/my_app_web/controllers/page_html.ex:2: module MyAppWeb.Components.CustomComponents.MyButton is not loaded and could not be found. This may be happening because the module you are trying to load directly or indirectly depends on the current module
    expanding macro: MyAppWeb.Components.__using__/1

Code for further reference:

#components.ex
defmodule MyAppWeb.Components do
  defmacro __using__(_) do
    quote do
      import unquote(__MODULE__).CustomComponents.{
        MyButton 
        # ....
      }
    end
  end
end

And the directory looks like this:
my_app_web/
|-- components/
| |-- custom_components/
| | |-- my_button.ex
| | |-- other_component1.ex
| |-- components.ex

We would need to see the code in my_button.ex to know for sure but it looks like you just don’t have the MyButon module properly defined. It must be:

defmodule  MyAppWeb.Components.CustomComponents.MyButton do
  # ...
end
1 Like

Saw this thread pop up and thought I’d share how I’ve ended up doing this.

I mostly work with umbrella applications, where we usually have a separate application which handles assets and components. Depending on the project, it’s been called either DesignSystem, WebAssets, CommonWeb or similar. In the most recent one it’s called DesignSystem so I’ll go with that one for the examples. All the components are in separate files and the folder structure is something along the lines of:

apps/
|-- design_system/
||-- assets/
|||-- css/
|||-- js/
||-- lib/
|||-- design_system/
||||-- components/
|||||-- form/
||||||-- error.ex
||||||-- input.ex
|||||-- button.ex
|||||-- table.ex
|||-- design_system.ex
...

Component’s module naming follows the folder structure.

defmodule DesignSystem.Components.Button do
  def button(assigns) do
    ...
  end
end

In design_system.ex I have a defdelegate for every component, which allows me to call them without specifying the full module name.

defmodule DesignSystem do
  defdelegate button(), to: __MODULE__.Button
  defdelegate input(), to: __MODULE__.Form.Input
  ...
end

Verbose code is more to my liking, and it also allows easy code completion, so I don’t mind my .heex files looking like this:

<DesignSystem.box>
  <DesignSystem.button>
    Click me!
  </DesignSystem.button>
</DesignSystem.box>

To deal with typing out the DesignSystem part every time, I would either import or alias the module:

defmodule ExampleWeb do
  def live_view() do
    quote do
      use Phoenix.LiveView,
        layout: {ExampleWeb.Layouts, :live}

      # Either this
      import DesignSystem

      # Or this
      alias DesignSystem, as: DS

      unquote(html_helpers())
    end
  end

end

The reason I don’t do this is because in my view it’s easier for beginners to clearly see where the function is defined at. Also this one time I made a component called Form and I wanted to call it with <.form> but it’s already defined in Phoenix.Component, and I didn’t want to call it .simple_form or similar. So yeah, a ridiculous reason but I am a ridiculous person.

3 Likes