Getting started with React 360 and Elixir


We will be using Elixir, a little phoenix ( little ), and React 360. You can read up on each here:

It should be noted that it is, at the time of writing this, hard to find good supporting docs for React 360, it has been rebranded from React VR.



We need to meld two worlds here, but first we need to get a phoenix app up and running. Since we are not using ecto for this we disable that.

mix phovr --no-ecto

At this point we have our phoenix app up, you can at this point run it via

cd phovr
mix phx.server

to confirm.

Kill the server and lets get it ready for our React 360 configuration.

cd assets
rm -rf *

React 360


npm install -g react-360-cli

inside the assets directory:

react-360 init Hello360
setopt -s glob_dots
mv Hello360/*(N) ./
rmdir Hello360

Make sure you can get it up and running via:

npm start

VR Proxy configuration

Now that we have a React 360 app working, lets enable hot reloading.

We are going to create a proxy file that Elixir can initiate and have the proxy forward requests to our elixir.

in the assets create a proxy file with:

// redirect for api
const express = require('express'); //eslint-disable-line
const proxy = require('http-proxy-middleware'); //eslint-disable-line
const cors = require('cors');
const { spawn } = require('child_process');

const app = express();

app.use('/js/*', proxy({
  target: 'http://localhost:8081/',
  changeOrigin: true,
  pathRewrite: {
    '^/js': '/',
app.use('/images/*', proxy({
  target: 'http://localhost:8081/',
  changeOrigin: true,
  pathRewrite: {
    '^/images': '/static_assets',
const wsProxy = proxy('ws://localhost:4000', { changeOrigin: true });
app.use('/*', proxy({ target: 'http://localhost:4000', changeOrigin: true }));
app.use('/phoenix/', wsProxy);
app.use('/socket/', wsProxy);

const server = app.listen(4001, '');
server.on('upgrade', wsProxy.upgrade);

const start = spawn('yarn', ['start']);
start.stdout.on('data', (data) => {
  console.log(`stdout: ${data}`);

start.stderr.on('data', (data) => {
  console.log(`stderr: ${data}`);

start.on('close', (code) => {
  console.log(`child process exited with code ${code}`);

Since these are not dev deps, and outside of the needed scope we install the deps manually.

npm install express http-proxy-middleware cors child_process --dev

This will allow us to setup a redirect to the React 360 system as needed. We will rely on phoenix reload watcher for us to go have it reload.

Dev config

Inside your config directory and your dev.exs

look for the section around line 14:

watchers: [node: ["node_modules/brunch/bin/brunch", "watch", "--stdin",
                  cd: Path.expand("../assets", __DIR__)]]

and change it to:

watchers: [
  node: [
    cd: Path.expand("../assets", __DIR__)]

Add in a new pattern to watch for:


it should look like this now:

config :phovr, PhovrWeb.Endpoint,
  live_reload: [
    patterns: [

We also need to configure a flag for our dev mode vs prod mode so that we don't watch and hot reload the wrong location.

config :phovr, :js_renderer,
  is_prod: false

inside prod.exs

config :phovr, :js_renderer,
  is_prod: true


We are going to have phoenix tell the client what type of rendering to do. You need to configure the layout_view.ex to render the proper tag as needed.

defmodule PhovrWeb.LayoutView do
  use PhovrWeb, :view
  def js_script_tag do
    if Application.get_env(:phovr, :js_renderer)[:is_prod]  do
      ~s(<script src="/js/client.bundle.js?platform=vr"></script>)
      ~s(<script src="/js/client.bundle?platform=vr"></script>)

  def js_location_tag do
    if Application.get_env(:phovr, :js_renderer)[:is_prod]  do

  def css_link_tag do
    if Application.get_env(:phovr, :js_renderer)[:is_prod]  do
      ~s(<link rel="stylesheet" type="text/css" href="/css/app.css" media="screen,projection" />)

app html

For the app.html.eex we want to render differently.

<!DOCTYPE html>
<html lang="en">
    <style>body { margin: 0; }</style>
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">

    <%= render @view_module, @view_template, assigns %>
    <%= {:safe, js_script_tag() } %>

      // Initialize the React 360 application
      var getUrl = window.location;
      function getAppUrl(){
        var url = '<%= {:safe, js_location_tag() } %>';
        if (url.match(/^\//)) {
           return getUrl.protocol + "//" + + url;
        return url;
          assetRoot: 'images/',

For the index.html.eex we change it to just:

<div id="container"></div>


We are now ready to go back up to the root directory from the cli and run:

mix phx.server

You will then want to load up http://localhost:4001

We do this proxy so that you can test things on remote devices that have to go to different renderers. Go ahead and change the text from Welcome to React 360 to something else and watch it reload live.

When you close the server you will also need to kill the node process. I just use killall node to do it quickly


Because we have to package this puppy up differently and we don't use brunch, here is a quick overview.

cd apps/vr/assets
yarn install --ignore-engines
yarn bundle
mkdir -p ../priv/static/js || true
cp build/* ../priv/static/js

This will push all of the static files to the proper location for any type of packaging you might be doing for deployments.