DEV Community 👩‍💻👨‍💻

Jeremy Woertink
Jeremy Woertink

Posted on • Updated on

Render Component from Action in Lucky

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 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:

# src/components/
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"

    div class: "comments" do
      @comments.each do |comment|
Enter fullscreen mode Exit fullscreen mode

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

# src/actions/
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")
Enter fullscreen mode Exit fullscreen mode

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

# src/actions/api/comments/
class Api::Comments::Index < ApiAction
  get "/api/comments" do
    save_comment =
    comments = CommentQuery.all

    render_component CommentList, save_comment: save_comment, comments: comments
Enter fullscreen mode Exit fullscreen mode

Lastly, my javascript is pretty simple now.

import { Controller } from 'stimulus'

export default class extends Controller {

  connect() {
      .then(res => res.text())
      .then(html => element.innerHTML = html)
Enter fullscreen mode Exit fullscreen mode


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!

Top comments (0)

Top Heroku Alternatives (For Free!)

Recently Heroku shut down free Heroku Dynos, free Heroku Postgres, and free Heroku Data for Redis on November 28th, 2022. So Meshv Patel put together some free alternatives in this classic DEV post.