Render Component from Action in Lucky
Tags: lucky, crystal
Published: 2019-10-30 18:43:43

---
title: Render Component from Action in Lucky
published: true
description: Rending a component from an action in Lucky
tags: lucky, crystal
---

**EDIT**: Since this writing, Lucky now has a `component` method (built-in) you can use in your actions. No need to create a custom macro!

For one of my Lucky apps, I'm using [Stimulus](https://stimulusjs.org/) which is light-weight. I wanted to avoid doing a ton on the client side so that way I can keep all the nice benefits of Crystal and Lucky.

In a recent instance, I had to load some comments on to a page asynchronously. Normally in this case, you might write some javascript to make an api call, then gather all of your records in a giant JSON object. Then you'd probably iterate over the result, and create some sort of component. Maybe in Vue, or in React, and have those render each comment. With Stimulus, you don't have built-int templating. You're left with writing a lot of `document.createElement`, and `element.setAttribute` type stuff. Or you just use lots of `innerHTML = ''` type calls. 

What I wanted was to write my markup in Lucky, then make an API call that returns the markup, and I can just shove that in to an element and be done with it. 

By default, the actions in Lucky want you to render an HTML Page. But I didn't want to make a blank layout just for these. So here's what I did:


```crystal
# src/components/comments_list.cr
class CommentsList < BaseComponent
  needs save_comment : SaveComment
  needs comments : CommentQuery

  # Renders the comments form, and each comment
  def render
    # This could also move to it's own component!
    form_for Comments::Create do
      textarea save_comment.body, class: "field"
      submit "Post Comment"
    end

    div class: "comments" do
      @comments.each do |comment|
        mount Comment.new(comment)
      end
    end
  end
end
```

Now I just need a handy helper macro in my ApiAction.


```crystal
# src/actions/api_action.cr
abstract class ApiAction < Lucky::Action
  accepted_formats [:json], default: :json

  macro render_component(component_class, **assigns)
    send_text_response({{ component_class }}.new(
      {% for key, value in assigns %}
        {{ key }}: {{ value }},
      {% end %}
    ).render_to_string, "text/html")
  end
end
```

Then I can use this new macro in my api actions!

```crystal
# src/actions/api/comments/index.cr
class Api::Comments::Index < ApiAction
  get "/api/comments" do
    save_comment = SaveComment.new
    comments = CommentQuery.all

    render_component CommentList, save_comment: save_comment, comments: comments
  end
end
```

Lastly, my javascript is pretty simple now.

```javascript
import { Controller } from 'stimulus'

export default class extends Controller {

  connect() {
    fetch("/api/comments")
      .then(res => res.text())
      .then(html => element.innerHTML = html)
  }
}
```


## Thoughts

I know I left out several bits like, setting up stimulus, or what the main action / page look like, but all of those should be fairly straight forward. I'll probably do a writeup later on integrating stimulus with Lucky. Hopefully these examples should get you close should you need to use this!