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
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.
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
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.
Popular in Discussions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance









