Elixir Phoenix + Brunch + Redux + React

This is mostly a post to remember how to setup this in the future.

I have been learning Redux since I found out that Elm influenced it. This is how I set up a new project with Elixir Phoenix, Redux and React. The important thing is that I'll be using the brunch that is configured by default in Phoenix instead of webpack. Why?, because right now I don't need webpack and brunch is already there.

First create a new Phoenix project and accept to fetch and install the dependencies:

mix phoenix.new phoenix_redux

Move inside the just created directory and setup the database.

cd phoenix_redux
mix ecto.create

Install the react, redux and react-redux dependencies

npm install --save react react-dom redux react-redux

We need to add support for ES2015

npm install --save-dev babel babel-preset-es2015 babel-preset-react

We need to modify the brunch-config.js file to configure babel to understand react and ES2015. Add the presets to the babel section:

plugins: {
  babel: {
    // Do not use ES6 compiler in vendor code
    ignore: [/web\/static\/vendor/],
    presets: [ "es2015", "react" ]
  }
},

And whitelist the packages:

npm: {
  enabled: true,
  whitelist: ["phoenix", "phoenix_html", "react",
    "react-dom", "redux", "react-redux"]
}

Finally let's try it. Create a reducers directory inside web/static/js/

mkdir web/static/js/reducers

and create an index.js file in it:

export default (state = 0, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return state
  }
}

Now create a components directory:

mkdir web/static/js/components

and add a Counter.js file

import React from 'react'

const Counter = ({value, onIncrement, onDecrement}) => (
    <div>
        <h1>{value}</h1>
        <button onClick={onIncrement}>+</button>
        <button onClick={onDecrement}>-</button>
    </div>
)

export default Counter

Then add an App.js file

import { connect } from 'react-redux'
import Counter from './Counter'

const mapStateToProps = (state) => {
    return {
        value: state
    }
}

const mapDispatchToProps = (dispatch) => {
    return {
        onIncrement: () => { dispatch({type: 'INCREMENT'}) },
        onDecrement: () => { dispatch({type: 'DECREMENT'}) },
    }
}

const App = connect(mapStateToProps, mapDispatchToProps)(Counter)

export default App

Finally replace the contents of web/static/js/app.js with this:

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import rootReducer from './reducers'
import App from './components/App'

let store = createStore(rootReducer)

render(
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById('root')
)

Remove the

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

in web/templates/layout/app.html.eex and add this instead of it:

<div id="root"></div>

Start the app

mix phoenix.server

Go to http://localhost:4000 and test that everything works:

It works!It works!

That's it.

Sources: https://github.com/thestonefox/phoenix_react_redux_tutorial