Achieve more with Less code, Elm vs React-Redux-Flow

Elm Language was specifically designed to build front-end applications, and it offers a great experience as a whole. Its most shiny features are:

  1. Great performance using the virtual DOM
  2. Functional language
  3. Safety through a Powerful type system
  4. State management using the built-in Elm architecture
  5. A powerful and helpful compiler

We will see how to achieve 3, 4 and 5 using React, Redux and Flow.

Let's look at some Elm code:

import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)

main =
  Html.beginnerProgram
    { model = model
    , view = view
    , update = update
    }

-- MODEL
type alias Model = Int

model : Model
model =
  0

-- UPDATE
type Msg
  = Increment
  | Decrement


update : Msg -> Model -> Model
update msg model =
  case msg of
    Increment ->
      model + 1

    Decrement ->
      model - 1

-- VIEW
view : Model -> Html Msg
view model =
  div []
    [ button [ onClick Decrement ] [ text "-" ]
    , div [] [ text (toString model) ]
    , button [ onClick Increment ] [ text "+" ]
    ]

https://gist.github.com/haikyuu/abd1145667e02aee2332d55e0d99bd33

The beauty of Elm is that every Elm app is written using the same parts:

  • Model: what is the state of your app

  • Update: how to update the state

  • View: how to display the state

The syntax is a bit weird for newcomers but it just takes some getting used to.

And its equivalent (commented) using React, Redux and Flow:

//@flow
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, bindActionCreators } from 'redux'
import { Provider, connect } from 'react-redux'

type Increment = {
  type: "INCREMENT"
}
type Decrement = {
  type: "DECREMENT"
}
type CounterActions =
  | Increment
  | Decrement

type Action = CounterActions // whenever we add a reducer, we add its actions here

//make sure dispatch can only dispatch our predefined actions
//and returns the action dispatched
type domainIdentity<T> = <A: T>(a: A) => A
type Dispatch = domainIdentity<Action>


type CounterState = number

//reducer
const counter = (state: CounterState = 0, action: CounterActions) =>{
  switch (action.type) {
    case 'INCREMENT':
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      (action: empty); //to make sure we handle all the actions
      //it will trigger a flow error when we forget a case !!
      return state
  }
}

//action creators
const increment = (): Increment => ({ type: "INCREMENT" })
const decrement = (): Decrement => ({ type: "DECREMENT" })

// our global state, also needs to be modified whenever we add a reducer
type State = CounterState

// passing state to our component as props
const mapStateToProps = (state: State) => ({state})

const mapDispatchToProps = (dispatch: Dispatch) => bindActionCreators({
  increment,
  decrement,
}, dispatch)


//helper flow types .. should be in a config file
type _ExtractReturn<B, F: (...args: any[]) => B> = B;
/*export*/ type ExtractReturn<F> = _ExtractReturn<*, F>;

//redux state and action creators types
type ReduxProps = ExtractReturn<typeof mapStateToProps>;
type ReduxActions = ExtractReturn<typeof mapDispatchToProps>;

//View
//we don't have any props except redux ones
type Props = {} & ReduxProps & ReduxActions
const App = ({ decrement, increment, state }: Props) =>(
  <div>
    <button onClick={decrement}>-</button>
    <div>{state}</div>
    <button onClick={increment}>+</button>
  </div>
)
//connect our component with the redux store
const ConnectedApp = connect(mapStateToProps, mapDispatchToProps)(App)

let store = createStore(counter)

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

https://gist.github.com/haikyuu/a540268b3336a52de69ae94d8e3b9e3c

A few things to note:

  • Elm’s type system is much more powerful and descriptive than Flow (and typescript)

  • The Elm architecture is integrated in the language level, so you can achieve more with less code in Elm compared to how much boilerplate you need to write in Redux.

  • Redux was inspired from Elm architecture

If you cannot use Elm in your day job, you can still benefit from similar goodies using Flow.

You will also become a better React developer by learning Elm, so check it out.