# Guest Post: Building a Reusable Card Component in Phoenix LiveView

This is a guest post from Sophie DeBenedetto, co-author of [_Programming Phoenix LiveView_](https://pragprog.com/titles/liveview/programming-phoenix-liveview/). You can [get started](/content/docs/elixir/index.html) with your own Elixir Phoenix application in a jiffy!

You may already be reaching for LiveView components to wrap up the behavior and markup of distinct portions of your LiveView UI. In this post, we’re going to take a single-purpose component that displays book review data in Bootstrap-style card format, and transform it into a highly-reusable component that you can use throughout your application, using LiveView component slots.

We’ll start with a simple component that uses the default slot, then we’ll use named slots and teach our component to yield variables back up to the caller. Finally, we’ll use iteration and named slots to dynamically render nested function components.

## The Goal

We want to be able to dynamically render content within the markup of a [Bootstrap card](https://getbootstrap.com/docs/4.0/components/card/). When we’re done, we’ll have a set of nested function components that iterates over a list of items, and uses named slots to render a card for each one. Our function components will be generic and reusable—they won’t have awareness of what content they’re rendering.

## The Feature: Display Book Reviews

In this post, we’ll build a Bootstrap-style card component and use it to render a set of book reviews like this:

We’ll use our reusable card component to render each book review’s details, including the number of stars, the email of the user who left the review, and the details of the review.

### The Simple Card Component

First up, we’ll define a module that implements a `cards/1` function component to iterate over book reviews and display each one in a Bootstrap card.

```elixir
defmodule LiveLibrary.CardComponent do
  use Phoenix.Component

def cards(assigns) do
    ~H"""
    <%= for review <- @reviews do %>
      <div class="card review-card">
        <div class="card-header">
          <%= for _ <- 0..review.stars do %>
            <i class="bi bi-star-fill"></i>
          <% end %>
        </div>
        <div class="card-body">
          <h5 class="card-title">
            <strong>by: <%= review.user.email %></strong>
          </h5>
          <p class="card-text">
            <%= review.notes %>
          </p>
        </div>
      </div>
    <% end %>
    """
  end
end
```

Our function component is simple enough—it expects the provided assigns to include a `@reviews` assignment. It iterates over those reviews and renders a Bootstrap card to display the details of each one.

### Dynamic Components with the Default Slot

We’ll start by taking advantage of a component feature called “slots”. Slots allow us to provide our component with blocks of HTML to render via a simple syntax:

```elixir
<CardComponent.cards reviews={@book.reviews} >
<!-- some content -->
</CardComponent.cards>
```

As an intermediate step, we’ll start with a simplified version of our function component that _only_ renders the card header:

```elixir
def cards(assigns) do
  ~H"""
  <%= for review <- @reviews do %>
    <div class="card review-card">
      <div class="card-header">
        <%= for _ <- 0..review.stars do %>
          <i class="bi bi-star-fill"></i>
        <% end %>
      </div>
    </div>
  <% end %>
  """
end
```

### Extending The Component With Named Slots

We’ll implement a named slot for each of the sections of the card component we need to inject content into.

```elixir
<CardComponent.cards items={@book.reviews} >
  <:header let={review}>
    <%= for _ <- 0..review.stars do %>
      <i class="bi bi-star-fill"></i>
    <% end %>
  </:header>

<:title let={review}>
    by: <%= review.user.email %>
  </:title>

<:body let={review}>
    <%= review.notes %>
  </:body>
</CardComponent.cards>
```

This allows our component to use `render_slot/2` to render dynamic content based on variable assignments from a parent component. With this, we’re left with a highly dynamic function component that wraps up some card markup and leaves the awareness of the content entirely up to the caller.

### Iteratively Render Nested Function Components

We can refactor our component module to implement a new function component, `card/1`, followed by an update to the `cards/1` component to iterate over the list of items in the assigns and render a card component for each one:

```elixir
def card(assigns) do
  ~H"""
  <div class="card review-card">
    <div class="card-header">
      <%= render_slot(@header, @item) %>
    </div>
    <div class="card-body">
      <h5 class="card-title">
        <strong><%= render_slot(@title, @item) %></strong>
      </h5>
      <p class="card-text">
        <%= render_slot(@body, @item) %>
      </p>
    </div>
  </div>
  """
end
```

Finally, we can iteratively render a `card` for each item in the `cards/1` function:

```elixir
def cards(assigns) do
  ~H"""
  <%= for item <- @items do %>
    <.card header={@header} title={@title} body={@body} item={item} />
  <% end %>
  """
end
```

With that, we’ve built a reusable `cards/1` component that can render a list of cards without needing to know the specific content it will display.

## Wrap Up

In this example, we began with a single-purpose Bootstrap card LiveView component, and converted it into a versatile function component with the help of component slots. You’re ready to go out and build some reusable function components of your own.
