I just started learning React after 2 years of Angular, and I'm surprised at how fun React is and how amazing the community and supporting packages are. I'm also a huge fan of Crystal and the Lucky framework, so what could be more awesome than using these tools together?

In this post I'm going to show you how you can add React components to your Lucky applications with Laravel mix and lucky-react.

Demo

To see code for this feature you can clone the lucky_demo repo and checkout the lucky-react branch.



git clone git@github.com:mikeeus/lucky_demo.git cd lucky_demo bin/setup git checkout lucky-react

Otherwise you can follow along with a fresh lucky app by running lucky init app_name .

Laravel Mix

Since we'll be using jsx to write React components we need to update Laravel mix's config to compile our JavaScript correctly. Laravel mix makes it easy to set it up by changing our mix.js() call in the configuration to mix.react() .



// webpack.mix.js mix // ... . react ( " src/js/app.js " , " public/js " ) // instead of .js(...)

Babel Plugin: transform-class-properties (Optional)

In order to use arrow functions and other awesome syntax we need babel's transform-class-properties plugin. Laravel mix does not come with this plugin by default so we need to install it.

yarn add babel-plugin-transform-class-properties

Then we add a .babelrc file in the root of our project with the following content:



{ "plugins" : [ "transform-class-properties" ] }

This will be picked up by mix automatically! Dope.

Writing React Components

Now that we have support for jsx, we can write components and import them into our app.js file. For organization I put my components in the src/js/components/ directory.

Here is the Bordered component and a simplified version of the Chat component that are used in the demo app.



// src/js/components/Bordered.jsx import React from ' react ' ; export class Bordered extends React . Component { render () { return ( < div style = {{ border : ' 2px solid ' }} > { this . props . children } < /div > ) } }

// src/js/components/Chat.jsx import React from ' react ' ; import { ChatInput } from ' ./ChatInput ' ; import { Message } from ' ./Message ' ; export class Chat extends React . Component { ... render () { return ( < div style = { styles } > < h2 style = { styles } > Conversation < /h2 > < div > { this . state . messages . map ( message => < Message key = { message . id } sender = { message . sender } text = { message . text } / > ) } < /div > < ChatInput writeMessage = { this . onWriteMessage } / > < /div > ) } }

LuckyReact

To allow rendering React components in Lucky apps I've created an npm module called lucky-react that adds event listeners on turbolinks:load and turbolinks:before-render to mount and unmount components using [data-react-class] and [data-react-props] attributes.

I've also created a crystal shard called lucky_react with helper methods for rendering elements with these attributes in your Lucky pages.

lucky-react npm module: Finds and renders React components on your pages using [data-react-class] and [data-react-props] attributes.

lucky_react crystal module: Adds helper methods for rendering elements with the right attributes so they can be found by lucky-react .

Lets use them now to render our Chat and Bordered components on our home page.

First install the npm module.



yarn add lucky-react

Then add the shard to shard.yml and run shards .



# shard.yml ... dependencies: ... lucky_react: github: mikeeus/lucky_react

Registering React Components

In order for LuckyReact to render our components we need to import and register them in our app.js file.



// src/js/app.js ... import LuckyReact from "lucky-react"; import { Chat } from './components/Chat'; import { Bordered } from './components/Bordered'; LuckyReact.register({ Chat, Bordered });

That's all we need to do! LuckyReact will create event listeners on turbolinks:load and turbolinks:before-render to mount and unmount these components if it finds them on the page.

Note that we only need to register Chat and Bordered since they are the only root components. ChatInput and Message are nested within Chat and will be handled automatically by React.

Rendering Components on Pages

Now in our Pages we can use the LuckyReact crystal module to add elements which reference our components so they can be rendered.



# src/pages/home/index_page.cr require "lucky_react" class Home::IndexPage < GuestLayout include LuckyReact # include the module def content react "Bordered" do # call react h1 "React Component" , style: "text-align: center;" end messages = [ { id: 1 , sender: "me" , text: "Hi" }, { id: 2 , sender: "Chatbot" , text: "Hi! How can I help?" }, { id: 3 , sender: "me" , text: "Can you tell me the time?" }, { id: 4 , sender: "Chatbot" , text: "Sure it's #{ Time . now } " } ] react "Chat" , { messages: messages } # with props end end

We can render components without the lucky_react shard by adding the [data-react-class] and [data-react-props] . The above example would then be written like this:



# src/pages/home/index_page.cr class Home::IndexPage < GuestLayout def content div "data-react-class" : "Bordered" do h1 "React Component" , style: "text-align: center;" end messages = [ { id: 1 , sender: "me" , text: "Hi" }, { id: 2 , sender: "Chatbot" , text: "Hi! How can I help?" } ] div "data-react-class" : "Chat" , "data-react-props" : ({ messages: messages }). to_json end end

If you run the app now and visit the home page you'll see the chat component working!

Join Us

I hope you enjoyed this tutorial and found it useful. Join us on the Lucky gitter channel to stay up to date on the framework or checkout the docs for more information on how to bring your app idea to life with Lucky.