100phlecs

100phlecs

How do you organize your components with Phoenix 1.7?

Are there any downsides, like perf issues, to putting all functional components in CoreComponents as long as you prefix it with the context for organization, i.e. blog_navbar, app_navbar ?
Previously I’ve sectioned them out by context into their own modules, but I think this may not have many benefits as I often prefix the function with its context.

What’s your approach?

Most Liked

fceruti

fceruti

I don’t like the CoreComponents module approach. To me it feels like that messy drawer in your house where you keep that extra battery, plastic sunglasses and all sort of random things.

What I like to do, is create more topical modules such as Layout, Input or Modal. Then you can alias them where you’d import CoreComponents and then call from anywhere <Layout.sidebar />, <Input.select />, <Modal.toast /> etc etc.

Maybe at first the benefits aren’t that great, but consider these case:

<Layout.sidebar /> v/s <.sidebar_layout />

How do you know where sidebar_layout is coming from? You’d need to check for an implementation in the current module, inside any of the imports, inside your use call or any of it’s imports. That can get really tricky as the project grows. Layout.sidebar stays O(1) for definition retrieval.

Another complexity CoreComponent introduces is the question to the developer: Should this component be a CoreComponent? That is not always easy to answer and ultimately meaningless. It’s the worst kind of decision to make.

19
Post #2
aiwaiwa

aiwaiwa

My 2 cents (feeling adventures)

flash_components.ex:

defmodule ProjectWeb.FlashComponents do
  @parent __MODULE__ |> Module.split() |> Enum.drop(-1) |> Module.concat()
  @core Module.concat(@parent, CoreComponents)

  defdelegate flash(assigns), to: @core
  defdelegate flash_group(assigns), to: @core
end

form_components.ex:

defmodule ProjectWeb.FormComponents do
  @parent __MODULE__ |> Module.split() |> Enum.drop(-1) |> Module.concat()
  @core Module.concat(@parent, CoreComponents)

  defdelegate simple_form(assigns), to: @core
  defdelegate button(assigns), to: @core
  defdelegate input(assigns), to: @core
  defdelegate label(assigns), to: @core
end

modal_components.ex:

defmodule ProjectWeb.ModalComponents do
  @parent __MODULE__ |> Module.split() |> Enum.drop(-1) |> Module.concat()
  @core Module.concat(@parent, CoreComponents)

  defdelegate modal(assigns), to: @core

  alias Phoenix.LiveView.JS
  defdelegate show_modal(js \\ %JS{}, id), to: @core
  defdelegate hide_modal(js \\ %JS{}, id), to: @core
end

show_components.ex:

defmodule ProjectWeb.ShowComponents do
  @parent __MODULE__ |> Module.split() |> Enum.drop(-1) |> Module.concat()
  @core Module.concat(@parent, CoreComponents)

  defdelegate header(assigns), to: @core
  defdelegate table(assigns), to: @core
  defdelegate list(assigns), to: @core
  defdelegate back(assigns), to: @core

  alias Phoenix.LiveView.JS
  defdelegate show(js \\ %JS{}, selector), to: @core
  defdelegate hide(js \\ %JS{}, selector), to: @core
end

error_components.ex:

defmodule ProjectWeb.ErrorComponents do
  @parent __MODULE__ |> Module.split() |> Enum.drop(-1) |> Module.concat()
  @core Module.concat(@parent, CoreComponents)

  defdelegate error(assigns), to: @core

  defdelegate translate_error(params_tuple), to: @core
  defdelegate translate_errors(errors, field), to: @core
end

And certainly project_web.ex will have to rewire:

  defp html_helpers do
    quote do
      # ...
      # import ProjectWeb.CoreComponents
      import ProjectWeb.FormComponents
      import ProjectWeb.FlashComponents
      import ProjectWeb.ModalComponents
      import ProjectWeb.ShowComponents
      import ProjectWeb.ErrorComponents
odd

odd

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.

Where Next?

Popular in Discussions Top

PragTob
Hello everyone, I know we had quite some threads (read through lots of them) about background job processing but it remains a hotly deba...
New
blackode
Elixir Upgrading is so Simple in Ubuntu and It worked for me Ubuntu 16.04 git clone https://github.com/elixir-lang/elixir.git cd elixir...
New
owaisqayum
I have a sample string sentence = "Hello, world ... 123 *** ^%&amp;*())^% %%:&gt;" From this string, I want to only keep the integers, ...
New
arpan
Hello everyone :wave: Today I am very excited to announce a project that I have been working on for almost 3 months now. The project is...
New
pillaiindu
I want to convert a Phoenix LiveView CRUD website to a CRUD mobile app. What do you think is the easiest way to do so?
New
ben-pr-p
In general I’ve been sticking to this community style guide GitHub - christopheradams/elixir_style_guide: A community driven style guide ...
New
chulkilee
Here are the list of HTTP client libraries/wrappers, and some thoughts on HTTP client in general. I’d like to hear from others how they w...
New
AstonJ
Can you believe the first professionally published Elixir book was published just 8 years ago? Since then I think we’ve seen more books f...
New
AstonJ
If so I (and hopefully others!) might have some tips for you :slight_smile: But first, please say which area you’re finding most challen...
New
opsb
We’re considering our architecture from a viewpoint of scaling our traffic heavily over the next 6 months. Our current deployment is runn...
New

Other popular topics Top

New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
274 41539 114
New
hariharasudhan94
lets say i have a sample like a = 20; b = 10; if (a &gt; b) do {:ok, "a"} end if (a &lt; b) do {:ok, b} end if (a == b) do {:ok, "equa...
New
jaysoifer
Is there a way to rollback a specific migration and only that one (“skipping” all the other ones)? Would mix ecto.rollback -v 200809061...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 36128 110
New
dogweather
I wrote this comment on r/haskell, and it’s not popular there. :wink: But I think I’m on to something… Haskell reminds me of Java, and e...
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New

We're in Beta

About us Mission Statement