RePh: React + Phoenix app scaffolding made easy
Long ago I wrote couple of articles about building RePh-stack based apps. It could be not that simple to follow due to dependencies became obsolete, backward-incompatible and many other reasons. I love RePh stack and pretty much sure it’s something everyone should consider when developing an app which interacts a lot with user on client side and I’ve created a lot of such apps, but often there was so much of boilerplate I had to copy-paste from previous apps... Finally I got tired of it and decided to create a mix reph.new generator which will act same way as mix phx.new but add all the boilerplate I need for React to integrate. The project repo can be found here: https://github.com/reph-stack/reph. In future I’ll probably add ability to generate simple commonly-used pieces of apps like authentication and admin page, but today I’m going to tell you about what mix reph.new task already have. Primary components RePh stack includes are:
- Phoenix 1.3
- React
- React Server-side rendering (SSR)
- Less
- Webpack 2
mix reph.new
This task is very similar to mix phx.new, it is based on Phoenix 1.3-rc installer and support all it’s arguments except for --no-brunch, --no-html, --dev and --ecto. To use the task, it have to be first installed. It can be achieved in a few ways, but the simplest is following:
mix archive.install github reph-stack/reph
Now you have reph.new task globally available and can use it wherever you want. Let’s generate a demo app and see what’s going on in there:
mix reph.new demo_app --module=DA
cd demo_app
First, you can run tests to compile the app for test environment and to ensure everything is fine. test/test_helper.exs file have instructions to recompile JS assets every time you run tests, so as soon as you run mix test, webpack will compile your assets and since we have server-side rendering (SSR), we can even test against what JS will produce for different requests in our Elixir tests. Unfortunately it costs a few seconds and bigger your app gets, more time it takes to compile so you can choose to either remove code for recompilation, or add your desired logic to cache compiled files between test runs. Some day I’ll probably try to resolve this. Next, you can run mix phx.server as for regular Phoenix application and check out you’ve got React code rendered into your page at http://localhost:4000.
Webpack
In assets/ directory there’s a webpack.config.js file responsible for assembling your assets. It consists of 2 configs: one for client-side bundle assembling and another for server-side. If you are not new to Webpack, feel free to update it, otherwise I think defaults should be good to start with.
Styles
In Phoenix you had a few .css files generated by default, in RePh you can use either .css or .less; also you can update webpack.config.js to include another (say SASS) styles processor and use it. This is why we have assets/styles directory in RePh rather than assets/css. By default, RePh app will include 3 style files: styles/index.less, which includesstyles/app.less and styles/phoenix.css. phoenix.css is default Phoenix styles based on Bootstrap and app.less is for your custom styles, feel free to create more and include it to styles/index.less. Bootstrap was moved out of assets in favour of CDN version (I believe it’s a better approach) however you can easily embed it into your project by adding bootstrap contents into assets/bootstrap.css and then include it into assets/index.less with @import "app.less";
JS
assets/js directory is the hearth of our React app. index.js is entrypoint for client-side bundle while server-side bundle have containers/index.js as the one. Application have 1 pre-set reducer reducers/ws.js which is responsible for websocket connection and channels management and 1 pre-set action actions/ws.js responsible for logic of working with the reducer. In the actions/ws.js you can connect to channels you need when page loads:
const actions = {
init: () => {
return (dispatch, getState) => {
dispatch(actions.socket_connect());
const blog_params = {};
dispatch(actions.channel_join(
"blog:no_category",
"blog",
blog_params));
}
}
//...
};
As you can see, actions.channel_join takes 3 arguments. First is channel “address” — the one you use to connect to it, second is name — this is how you call it on client side, and the last is params to be passed to channel init function. Also, you can define callbacks in setupHandlers function. For example:
const setupHandlers = (name, channel, dispatch, getState) => {
switch (name) {
// "blog" is channel name defined in call to "channel_join"
case "blog":
channel.on("post_new", (data) => {
// do something with data
});
break;
default:
break;
}
};
Application also have 2 basic components — components/Main and components/Header and 1 wrapper around it — containers/App. All three creates UI for the Phoenix default page you see at http://localhost:4000. Store located in store/index.js is a key component of React app responsible for SSR working properly and in most cases you wouldn’t need to change it unless you’ve got serious and that way you obviously know what and how to do. Now you can start writing your RePh apps with much less efforts :)
P.S.
If you like the project or feel like there should be something else in it, feel free to drop me a message or open an issue in the project repo. I hope you will love RePh stack as much as I do :)