What's the best solution for reusable view components?

I’ve got two models, Chores and Users, as part of a little chore-tracking CRUD app. When you go to the index route for either model, I display all the entries in pretty similar forms: a <table> with a <th> row and each database entry as a <tr> row.

As part of the app, when you view a single user (i.e. /users/:id), you can see all the chores that this user needs to take care of. I display these chores using the exact same table as I do at /chores, just with a filtered set of rows.

Here’s my problem: now I’ve got two different files (single_user.html and chores.html) that have the exact same code, and I have to worry about keeping them in sync. I would love to pull this code out somewhere, and in both of these places do something like @import chores-table and just pass in different lists.

I’m basically brand-new to Phoenix – maybe there’s a really obvious way to do this that I’m missing. Or maybe there are lots of ways! How do you make reusable components for your views?

1 Like

The guides are a good place to start:

https://hexdocs.pm/phoenix/views.html#sharing-views-and-templates

You’d create a separate file with the common parts and then render that from both of the files that use them, passing the list of chores in the last argument

2 Likes

Hi @ketzo, welcome to the Elixir forum! (late much :laughing: )

Even though Phoenix’s Views and Templates are really really powerful, I always caught myself wanting a little bit more “structure” to define components. I started toying around with the idea heavily inspired by ASP.NET’s View Components and later by GitHub’s View Components and Laravel Blade Components

Unfortunately, I didn’t have the chance to actually put this proof of concept to the test, but if you want to take some inspiration from it, here it is GitHub - thiagomajesk/viewplex: A convenience library that allows you to further separate your views into scoped components.

I hope to see something like this in Phoenix core someday, that bridges the gap between Live View and conventional HTML rendering.