DEV Community

Jason
Jason

Posted on

Comparing Elm to React/Redux

I have recently been exploring creating web apps in Elm and found it to be a breath of fresh air compared to the usual React/Redux projects I have worked on in the past.

*Disclaimer: I still think React/Redux is great and viable for large teams if done correctly. This article will just explain my pain points with it while working on large teams at various companies, and why I think Elm can be a better alternative in some cases.

Pain Points

After awhile many of the React/Redux projects I have worked on become massive, with hundreds of reducers, hundreds of components, mixtures of epics, thunks, reselect selectors, sagas and custom middlewares. Hot module replacement becomes slow, build times become slow, runtime performance gets slow, audit scores get low scores, bundle size gets big and the app gets an increasingly large amount of runtime errors with every push.

I know this is not everyone's experience, and if you work somewhere that enforces strict rules during development, then you will not have all these problems. But chances are you have experienced a few of these pain points too. (And if you haven't experienced any of these pains, then good work, it's a difficult feat)

When I speak of development "rules", I don't mean linter rules and prettier. I mean things like not installing too many third party libraries, having proper code splitting for your modules, and performing weekly or monthly lighthouse audits to see where your team can improve.

The Solution

Elm has a beautiful ecosystem meant to prevent a lot of these pains. It comes with its own struggles too for sure, but worth it, in my opinion.

Advantages of Elm:

  • No runtime exceptions
  • Everything is immutable
  • Small bundle sizes
  • Built-in event emitter and global state store similar to Redux
  • Built-in router for single page apps
  • Built-in code formatter (like prettier)
  • Strong type system
  • Easy interop with JS
  • Amazing compiler error messages and fast compile times

These advantages lead to more reliable webapps, better DX, and a better experience for end users.

Comparing the Elm architecture to React/Redux

Learning Elm can seem like a daunting task, especially with all the new syntax and concepts, but this is what this article is aimed to help with and explain that it's really not that different to React.

Below, I have written the same app in Elm and React/Redux to show their similarities.

State

In Redux there is a global store used for saving application state, Elm has a similar concept called the Model, it is a strongly typed version of a store.

Redux initial state for a reducer

const initialState = {
  count: 0
}
Enter fullscreen mode Exit fullscreen mode

Elm initial model and typings

type alias Model =
  { count : Int }

initialModel =
  { count = 0 }
Enter fullscreen mode Exit fullscreen mode

The type alias in Elm ensures that nothing other than a number will ever be assigned in the count property.

Actions

In Redux, you need to write actions for triggering some state changes or side effects. Elm has Messages which are very similar, but typed!

Redux Actions

// action types
export const INCREMENT = 'INCREMENT'
export const DECREMENT = 'DECREMENT'

// actions
export const increase = () => ({ type: INCREMENT })
export const decrease = () => ({ type: DECREMENT })
Enter fullscreen mode Exit fullscreen mode

Elm Messages

type Msg = Increase | Decrease
Enter fullscreen mode Exit fullscreen mode

Reducers

For every redux action you create, you normally have a corresponding reducer. In Elm it is almost the same except you are forced to always have an update function (reducer) for every message (action).

Redux Reducers

export function myReducer(state = initialState, action) {
  switch (action.type) {
    case INCREMENT:
      return { count: state.count + 1 }

    case DECREMENT:
      return { count: state.count - 1 }

    default:
      return state
  }
}
Enter fullscreen mode Exit fullscreen mode

Elm Update

update msg model =
  case msg of
    Increase ->
      { model | count = model.count + 1 }

    Decrease ->
      { model | count = model.count - 1 }
Enter fullscreen mode Exit fullscreen mode

Everything is immutable in Elm, so to update a record (object) you must use the pipe | and new record syntax to return a new copy of the state with the updated property.

Components

Components in React are what create the view that will be rendered for users to see. Elm does not have components but just a single view function that will render.

React JSX

import React from 'react'
import { connect } from 'react-redux'
import { increase, decrease } from './reducer'

const App = ({ increase, decrease, count }) => (
  <div>
    <button type="button" onClick={increase}>+1</button>
    <div>{count}</div>
    <button type="button" onClick={decrease}>-1</button>
  </div>
)

// Connect to redux
const mapStateToProps = ({ count }) => ({ count })
const mapDispatchToProps = { increase, decrease }

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(App)
Enter fullscreen mode Exit fullscreen mode

Elm view function

view model =
  div []
    [ button [ onClick Increment ] [ text "+1" ]
    , div [] [ text <| String.fromInt model.count ]
    , button [ onClick Decrement ] [ text "-1" ]
    ]
Enter fullscreen mode Exit fullscreen mode

Connecting

In React/Redux, components do not automatically have access to the redux store or actions/reducers, they must explicitly be connected. Connecting can be done nicely with another library called react-redux. In Elm, everything automatically has access to all the message types and data in the store.

React/Redux

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

const store = createStore(myReducer)

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

Elm

main =
  Browser.sandbox
    { init = initialModel
    , update = update
    , view = view
    }
Enter fullscreen mode Exit fullscreen mode

Conclusion

So we created a simple counter app. Overall it was pretty painless, didn't require any of the boilerplate that redux needs, and had typed payloads! If you want to play with this example, check it out on ellie-app.

If this article intrigued you and you want to learn more about Elm, check out these resources:

Follow me on twitter! @rametta

Top comments (12)

Collapse
 
recss profile image
Kevin K. Johnson

Would it be more fair to compare Elm to ReasonML?

Collapse
 
rametta profile image
Jason

I don't think it would be more or less fair. The point was to show the similarities and how they are not much different, in order to get more people interested in it. If I compared Elm to Reason then not many people would relate to it.

Collapse
 
fhammerschmidt profile image
Florian Hammerschmidt

Absolutely. Both are statically typed languages which compile down to JS (strictly speaking, not ReasonML, but Bucklescript) with a sound type system (other than typescript).

But imho Reason is easier to learn for JS developers, especially React developers, because of syntax similarities. Also, interop (which you probably will need in a bigger project) is better in Reason.

Elm is a cleaner approach though, and is very fitting for smaller, self-contained projects.

Collapse
 
csaltos profile image
Carlos Saltos

Good idea !!

Collapse
 
josephthecoder profile image
Joseph Stevens

Great article! Best comparison Ive seen so far. Elm looks really clean.

Collapse
 
rametta profile image
Jason

Thanks!

Collapse
 
vageez profile image
Angelo

Sweet article!

I wonder, since Elm has a single view is the re-rendering when models change as efficient as React?

Will definitely try Elm out! I love the FP syntax!

Collapse
 
rametta profile image
Jason

Thanks! And apparently it's more efficient in rendering, than React, according to some benchmarks

Collapse
 
serzn1 profile image
Serge

Hi Jason.
Could you show a solution for thunks replacement (calling a bunch of actions after one action etc)?

Collapse
 
rametta profile image
Jason

You wouldn't necessarily want to call more than one action for any effect. Ideally you would have one message handler for one side effect and if you wanted to combine 2 or more together you would pipe them into one new message.

model |> update Something |> update Something2

Collapse
 
masiucd profile image
Marcell Ciszek Druzynski

Great post

Collapse
 
csaltos profile image
Carlos Saltos

If you like a video tutorial about this and more Elm content you may check udemy.com/course/elm-the-complete-...