This quick tutorial has only been tested with the following component versions. Beware trying to get this working with other versions. Phoenix is not yet 1.0 and it may change enough to make this no longer work.
Git Repo: https://github.com/jschoch/reflux_eventbroker_react_phoenix_elixir
Elixir 1.05 and Phoenix 0.15.0 components:
Bower components for reflux 0.2.7, react 0.13.3
I’ve been working with phoenix quite a bit lately for brng.us and idgon.com and thought to explain the asset pipeline for javascript build tool neophytes like myself.
React shares nicely until you start to break things up and isolate them (a good practice). This is a great blog post discussing how to get your components talking, but if you are isolating it doesn’t work. Reflux provides a nice model which can help you decompose your app, and manage events.
Firstly I just want to say .js is a ghetto, and I look forward to a day where browsers can support more languages than just .js
Phoenix has a very nicely automated setup for dealing with this mess. It uses brunch and inotify to detect and dynamically compile your assets. The pipeline looks something like this:
You may want to look at my ec2 setup here if you don’t already have erlang, elixir, brunch, phoenix all installed.
Also for reference is my repo for this tutorial.
The first commit adds the files we need and creates a bower.json
Then run bower install
The next commit adds the changes we need for our brunch config to include the new bower_components
If you run mix phoenix.server you should see the default phoenix app page, and notice that brunch is picking through your static files and compiling them for you. If you open your dev tools in your browser you should see the following.
Our first step is to create a few conventional directories to store our reflux stores and our react components.
Now we need to make a few react components to play with. You may want to use the react fiddle here to familiarize yourself with react. You can see the fiddle I used here.
To componentize this we will break it into 3 thing React components and 2 reflux object.
Here is the commit which I will break down below.
I’m not sure if this is needed, but this removes your bower_components from your babel conversions.
- ignore: [/^(web\/static\/vendor)/]
+ ignore: [/^(web\/static\/vendor)|(bower_components)/]
web/static/js/Actions.js we just setup an action for Reflux to listen to.
+export default Reflux.createActions([
+ "swap"
+]);
web/templates/page/index.html.eex
I removed the standard template and add a target div for React to bind to
<div id="mydiv">
our crap should go here
</div>
Finally comes all our js code. We have the following components
TODO: add graphviz
app.js -> Actions.js
-> stores/TheStore.js
-> MyDiv.js
-> components/BtnA.js
-> components/BtnB.js
Bower and brunch do all of the magic to include our js components into our app.js aggregated file. We only have to import the components we created above. app.js initializes React to target the div “mydiv” in our index.html.eex template. It also pulls in our code from the import MyDiv statement.
web/static/js/app.js
// this is for phoenix and it's live code reloading, or web socket connections
import {Socket} from "phoenix"
// pulls MyDiv.js into scope
import MyDiv from "./MyDiv";
React.render(
<MyDiv />,
document.getElementById("mydiv")
);
BtnA.js and BtnB.js are almost identical. They simply render a div with a button and a button label. They import our Actions and bind them to an event handler which calls Action.swap.
+import Actions from "../Actions"
// TheStore.js is our Reflux store
+import TheStore from "../stores/TheStore"
+
// create our react object, this exports the filename as the export name, in this case BtnA
+export default React.createClass({
// this mixin ties the state and events to our react component
+ mixins: [Reflux.connect(TheStore)],
// we simply initialize state.name with the name of the component to be
// printed to the console
+ getInitialState(){
+ return {"name":"btna"};
+ },
// this binds to the button onClick event
+ handleClick(){
+ console.log(this.state.name,"clicked",this.state.test);
+ Actions.swap(this.state.test)
+ },
// render gets called when the component is created or the state is updated
+ render(){
+ return (
+ <button onClick={this.handleClick}> This is BtnA </button>
+ )
+ }
+})
The mixins line does all of the magic. It binds the functions of the mixin to the react object, so we could call this.onSwap(arg) directly. It also does the work of calling the lifecycle methods from reflux [init, preEmit, should Emit].
With reflux you can use Reflux.listenTo(store,”onEventToListen”), however we are using Reflux.connect which will update our components state to whatever the reflux store transmits (via this.trigger({stateKey: newState}). This can be limited to a state key if you want to filter on specific state keys. In our case we are listening to every event. Connect also will initialize the state of the component it is connected to via the Reflux’s getInitialState function. The initiliazation now looks like this
TODO: add graphviz
TheStore.init -> TheStore.getInitialState -> BtnA.getInitialState
TheStore initializes to {test: true}, and that ends up merged with our BtnA state with a result of {test: true,name: “btna”}
To fire up the app cd into the root directory (with the mix.exs) and run
mix phoenix.server
Go ahead and click either button. In the console you can see that clicking the button will change each components state for the state key “name”.
Now that you can see how to componentize react and use reflux stores to broker events between your components, you can start to see how you might move away from having to talk to a REST API. Once you tie in Phoenix’s sockets, you can now just push all of your events down to your client, and per the name, have it react. Next up I will be writing about how to take this to the next level.
References:
Good examples for React: http://ricostacruz.com/cheatsheets/react.html
About this blog: This blog will discuss technology associated with my idgon.com project. I am using Elixir and Phoenix for my backend, and React.js and Reflux for my front end. I have a library called Trabant to experiment with graph database persistence for Elixir. The views expressed on this blog are my own, and are not that of my current employer.
About Me: I am a hobbyist programmer interested in distributed computing. I dabble in Elixir, Ruby, and Javascript. I can't spell very well, and I enjoy golf.