React Components In Lucky With Laravel Mix and lucky-react
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.