React Developer's Crash Course into Elm
Tags: elm, react, redux, javascript
Published: 2020-10-17 19:24:41


---
title: React Developer's Crash Course into Elm 
published: true
description: Learning Functional Programming has a high learning curve. However, if you have something familiar to base it off of, it helps a lot. If you know React &amp; Redux, this gives you a huge head start.
tags: Elm,React,Redux,JavaScript
canonical_url: https://jessewarden.com/2020/10/react-developers-crash-course-into-elm.html
cover_image: https://jessewarden.com/wp-content/uploads/2020/10/react-dev-crash-elm-course-10.17.2020.png
---

Learning Functional Programming has a high learning curve. However, if you have something familiar to base it off of, it helps a lot. If you know [React](https://reactjs.org/) & [Redux](https://redux.js.org/), this gives you a huge head start. Below, we’ll cover the basics of [Elm](https://elm-lang.org/) using React & Redux/Context as a basis to help make it easier to learn.

The below deviates a bit from the Elm guide, both in recommendations and in attitude. Elm development philosophy is about mathematical correctness, learning & comprehending the fundamentals, and keeping things as brutally simple as possible. I’m impatient, don’t mind trying and failing things 3 times to learn, and immersing myself in complexity to learn why people call it complex and don’t like it. I’m also more about getting things done quickly, so some of the build recommendations follow more familiar toolchains React, Angular, and Vue developers are used too which is pretty anti-elm simplicity.

Docs
----

To learn React, most start at the [React documentation](https://reactjs.org/docs/getting-started.html). They are \_really\_ good. They cover the various features, where they’re recommended, and tips/caveats a long the way. For Redux, I hate the new docs despite them working extremely hard on them. I preferred the original [egghead.io lesson by Dan Abramov](https://egghead.io/courses/getting-started-with-redux) on it.

To learn Elm, most recommend starting at the [Official Guide](https://guide.elm-lang.org/). It starts at the very beginning by building a simple app and walks you through each new feature. It focuses (harps?) on ensuring you know and comprehend the fundamentals before moving on to the next section.

Tools
-----

To build and compile, and install libraries for React apps, you install and use [Node.js](https://nodejs.org/en/). It comes with a tool called [npm](https://www.npmjs.com/) (Node Package Manager) which installs libraries and runs build and other various commands.

For Elm, you [install the elm tools](https://guide.elm-lang.org/install/elm.html). They’re available via npm, but given the versions don’t change often, it’s easier to just use the installers. They come with a few things, but the only ones that really matter day to day are the elm compiler and the elm REPL to test code quickly, like you’d do with the `node` command.

Developing
----------

The easiest, and most dependable long term way to build & compile React applications is [create-react-app](https://reactjs.org/docs/create-a-new-react-app.html). Webpack, Rollup, and bundlers are a path of pain, long term technical debt maintenance burdens… or adventure, joy, and efficient UI’s based on your personality type. Using create-react-app, you’ll write JavaScript/JSX, and the browser will update when you save your file. Without create-react-app, you’d manually start React by:

```javascript
ReactDOM.render(
  <h1>Hello, world!</h1>,
  document.getElementById('root')
)
```


Elm recommends you only use the compiler until your application’s complexity grows enough that you require browser integration. Elm Reactor currently sucks, though, so [elm-live](https://github.com/wking-io/elm-live) will give you the lightest weight solution to write code and have the browser automatically refresh like it does in create-react-app. It’s like [nodemon](https://www.npmjs.com/package/nodemon) or the [browser-sync](https://browsersync.io/) days of old. The story here isn’t as buttoned up as create-react-app. You install elm-live, but still are required to finagle with html and a root JavaScript file. Same workflow though; write some elm code in `Main.elm` and when you save your file, it refreshes the browser automatically. Starting Elm on your page is similar to React:

```javascript
Elm.Main.init({
    node: document.getElementById('myapp')
})
```


Building
--------

When you’re ready to deploy your React app, you run `npm run build`. This will create an optimized JavaScript build if your React app in the build folder. There are various knobs and settings to tweak how this works through package.json and index.html modifications. Normally, the build folder will contain your root index.html file, the JavaScript code you wrote linked in, the vendor JavaScript libraries you reference, and various CSS files. You can usually just upload this folder to your web server.

The Elm compiler makes a single JavaScript file from an elm file running `elm make`. This includes the Elm runtime, your Elm code compiled to JavaScript, and [optionally optimized](https://guide.elm-lang.org/optimization/asset_size.html) (but not [uglified](https://www.npmjs.com/package/uglify-js)). Like React, you initialize it with calling an init function and passing in a root DOM node. Unlike create-react-app, you need to do this step yourself in your HTML file or another JavaScript file if you’re not using the basic Elm app (i.e. `browser.sandbox` ).

Language
--------

React is based on [JavaScript](https://www.javascript.com/), although you can utilize [TypeScript](https://www.typescriptlang.org/) instead. While React used to promote classes, they now promote functions and function components, although they still utilize JavaScript [function declarations](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function) rather than [arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions).

```javascript
// declaration
function yo(name) {
  return `Yo, ${name}!`
}

// arrow
const yo = name => `Yo, ${name}!`
```


TypeScript would make the above a bit more predictable:

```typescript
const yo = (name:string):string => `Yo, ${name}`
```


Elm is a strongly typed functional language that is compiled to JavaScript. The typings are optional as the compiler is pretty smart.

```elm
yo name =
  "Yo, " ++ name ++ "!"
```


Like TypeScript, it can infer a lot; you don’t \_have\_ to add types on top of all your functions.

```elm
yo : String -> String
yo name =
  "Yo, " ++ name ++ "!"
```


Notice there are no parenthesis, nor semi-colons for Elm functions. The function name comes first, any parameters if any come after, then equal sign. Notice like Arrow Functions, there is no `return` keyword. All functions are pure with no side effects or I/O, and return \_something\_, so the return is implied.

Both languages suffer from String abuse. The TypeScript crew are focusing on adding [types to template strings](https://twitter.com/ahejlsberg/status/1300554189337104385) since this is an extremely prevalent to do in the UI space: changing strings from back-end systems to show users. Most fans of types think something with a String is untyped which is why they do things like [Solving the Boolean Identity Crisis](https://www.youtube.com/watch?v=6TDKHGtAxeg).

Mutation
--------

While much of React encourages immutability, mutation is much easier for many people to understand. This is why tools like [Immer](https://github.com/immerjs/immer) are so popular for use in Redux. In JavaScript, if you want to update some data on a Person Object, you just set it.

```javascript
person = { name : "Jesse" }
person.name = "Albus"
```


However, with the increase in support for immutable data, you can use Object [Destructuring Assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) to not mutate the original object:

```javascript
personB = { ...person, name : "Albus" }
```


In Elm, everything is immutable. You cannot mutate data. There is no `var` or `let`, and everything is a `const` that is \_actually\_ constant (as opposed to JavaScript’s `const myArray = []` which you can still `myArray.push` to). To update data, you destructure a similar way.

```elm
{ person | name = "Albus" }
```


HTML
----

React uses [JSX](https://reactjs.org/docs/introducing-jsx.html) which is an easier way to write HTML with JavaScript integration that enables React to ensure your HTML and data are always in sync. It’s not HTML, but can be used inside of JavaScript functions, making the smallest React apps just 1 file. All JSX is assumed to have a root node, often a div if you don’t know semantic HTML like me. Just about all HTML tags, attributes, and events are supported. Here is an h1 title:

```javascript
<h1>Hello, world!</h1>
```


Elm uses pure functions for everything. This means html elements are also functions. Like React, all HTML tags, attributes, and events are supported. The difference is they are imported from the [HTML module](https://package.elm-lang.org/packages/elm/html/latest/) at the top of your main Elm file.

```elm
h1 [] [ text "Hello, world!" ]
```


Components
----------

In React, the draw is creating components, specifically [function components](https://reactjs.org/docs/components-and-props.html). React is based on JavaScript. This means you can pass dynamic data to your components, and you have the flexibility on what those Objects are and how they are used in your component. You can optionally enforce types at runtime using [prop types](https://reactjs.org/docs/typechecking-with-proptypes.html#gatsby-focus-wrapper).

```javascript
function Avatar(props) {
  return (
    <img className="Avatar"
      src={props.user.avatarUrl}
      alt={props.user.name}
    />
  )
}
```


In Elm, there are 2 ways of creating components. The first is a function. The other advanced way when your code gets larger is a separate file and exporting the function via [Html.map](https://package.elm-lang.org/packages/elm/html/latest/Html#map). Elm is strictly typed, and types are enforced by the compiler, so there is no need for runtime enforcement. Thus there is no dynamic `props`, rather you just define function arguments. You don’t have to put a type definition above your function; Elm is smart enough to “know what you meant”.

```elm
avatar user =
  img
    [ class "Avatar"
    , src user.avatarUrl
    , alt user.name ]
    [ ]
```


View
----

In React, your View is typically the root component, and some type of Redux wrapper, like a Provider.

```javascript
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  rootElement
)
```


In Elm, this is a root method called `view` that gets the store, or `Model` as it’s called in Elm as the first parameter. If any child component needs it, you can just pass the model to that function.

```elm
view model =
  app model
```


mapStateToProps vs Model
------------------------

In React, components that are connected use the `mapStateToProps` to have an opportunity to snag off the data they want, or just use it as an [identity](https://lodash.com/docs/4.17.15#identity) function and get the whole model. Whatever `mapStateToProps` returns, that is what your component gets passed as props.

```javascript
const mapStateToProps = state => state.person.name // get just the name
const mapStateToProps = state => state // get the whole model
```


In Elm, your Model is always passed to the view function. If your view function has any components, you can either give them just a piece of data:

```elm
view model =
  app model.person.name
```


Or you can give them the whole thing:

```elm
view model =
  app model
```


In React, you need to configure the `connect` function take this `mapStateToProps` function in when exporting your component.

In Elm, you don’t have to do any of this.

Action Creator vs Messages
--------------------------

In React, if you wish to update some data, you’re going to make that intent known formally in your code by creating an Action Creator. This is just a pattern name for making a function return an Object that your reducers will know what to do with. The convention is, at a minimum, this Object contain a `type` property as a String.

```javascript
const addTodo = content =>
  ({
    type: ADD_TODO,
    content
  })
// Redux calls for you
addTodo("clean my desk")
```


In Elm, you just define a type of message called `Msg`, and if it has data, the type of data it will get.

```elm
type Msg = AddTodo String
-- to use
AddTodo "clean my desk"
```


In React, Action Creators were originally liked because unit testing them + reducers was really easy, and was a gateway drug to pure functions. However, many view them as overly verbose. This has resulted in many frameworks cropping up to “simplify Redux”, including React’s built-in Context getting popular again.

In Elm, they’re just types, not functions. You don’t need to unit test them. If you misspell or mis-use them, the compiler will tell you.

View Events
-----------

 In React, if a user interacts with your DOM, you’ll usually wire that up to some event.

```javascript
const sup = () => console.log("Clicked, yo.")

<button onClick={sup} />
```


In Elm, same, except you don’t need to define the handler; Elm automatically calls the `update` function for you. You just use a message you defined. If the message doesn’t match the type, the compiler will yell at you.

```elm
type Msg = Pressed | AddedText String

button [] [ onClick Pressed ] -- works
input [] [ onChange Pressed ] -- fails to compile, input passes text but Pressed has no parameter
input [] [ onChange AddedText ] -- works because input changing will pass text, and AddedText has a String
```


mapDispatchToProps vs Msg
-------------------------

In React Redux, when someone interacts with your DOM and you want that event to update your store, you use the `mapDispatchToProps` object to say that a particular event fires a particular Action Creator, and in your component wire it up as an event via the props. Redux will then call your reducer functions.

```javascript
const increment = () => ({ type: 'INCREMENT' }) -- action creator
const mapDispatchToProps = { increment }
const Counter = props =>
( <button onClicked={props.increment} /> )

export default connect(
  null,
  mapDispatchToProps
)(Counter)
```


In Elm, we already showed you; you just pass your message in the component’s event. Elm will call update automatically. The update is basically Elm’s reducer function.

```elm
type Msg = Increment
button [] [ onClick Increment ]
```


Store vs Model
--------------

In Redux, you store abstracts over “the only variable in your application” and provides an abstraction API to protect it. It represents your application’s data model. The data it starts with is what the default value your reducer (or many combined reducers) function has since it’s called with `undefined` at first. There is a bit of plumbing to wire up this reducer (or combining reducers) which we’ll ignore.

```javascript
const initialState = { name : 'unknown' }
function(state = initialState, action) {...}
```


In Elm, you first define your Model’s type, and then pass it to your browser function for the `init` function or “the thing that’s called when your application starts”. Many tutorials will show an `initialModel` function, but for smaller models you can just define inline like I did below:

```elm
type alias Model = { name : String }

main =
    Browser.sandbox
        { init = { name = "Jesse" }
        , view = view
        , update = update
        } 
```


There isn’t really a central store that you directly interact with in Redux. While it does have methods you can use before Hooks became commonplace, most of the best practices are just dispatching Action Creators from your components. It’s called store, but really it’s just 1 or many reducer functions. You can’t really see the shape of it until runtime, especially if you have a bunch of reducer functions.

In Elm, it’s basically same, but the Model DOES exist. It’s a single thing, just like your store is a single Object. That type and initial model you can see, both at the beginning of your app, and at runtime.

Reducers vs Update
------------------

The whole reason you use Redux is to ensure your data model is immutable and avoid a whole class of bugs that arise using mutable state. You also make your logic easier to unit test. You do that via pure functions, specifically, your reducer functions that make up your store. Every Action Creator that is dispatched will trigger one of your reducer functions. Whatever that function returns, that’s your new Store. It’s assumed you’re using Object destructuring, [Immutablejs](https://immutable-js.github.io/immutable-js/), or some other Redux library to ensure you’re not using mutation on your state. If you’re using TypeScript, you can turn on “use strict” in the compiler settings to ensure your switch statement doesn’t miss a possible eventuality.

```javascript
const updatePerson = (state, action) => {
  switch(action.type) {
    case 'UPDATE_NAME':
      return {...state, name: action.newName }
    default:
      return state
  }
}
```


Elm has no mutation, so no need to worry about that. Whenever a Msg is dispatched from your view, the Elm runtime will call update for you. Like Redux reducers, your job is to return the new Model, if any from that function. Like TypeScript’s switch statement strictness, Elm’s built in pattern matching will ensure you cannot possibly miss a case. Note that there is no need of a default because that can’t happen.

```elm
update msg model =
  case msg of
    UpdateName name ->
      { model | name = name }
  
```


JavaScript, TypeScript, and Elm however can still result in [impossible states](https://jessewarden.com/2019/02/easier-asynchronous-state-modelling-in-react-redux-or-hooks.html). You should really think about using the types fully to ensure [impossible states are impossible](https://www.youtube.com/watch?v=IcgmSRJHu_8).

Thunk & Saga vs Elm
-------------------

In React, as soon as you want to do something asynchronous in Redux, you need to reach for some way to have your Action Creators plumbing be async.

[Thunks](https://github.com/reduxjs/redux-thunk) are the easiest; you offload the async stuff to the code in your Components and it’s just a normal `Promise` that pops out an Action Creators at various times: before, during, after success, after failure.

[Saga’s](https://redux-saga.js.org/) are more advanced and follow the [saga pattern](https://microservices.io/patterns/data/saga.html). For situations where the back-end API’s are horrible, and you have to do most of the heavy lifting of orchestrating various services on the front-end, Saga’s offer a few advantages. They allow you to write asynchronous code in a pure function way. Second, they maintain state \_inside\_ the functions. Like closures, they persist this state when you invoke them again and still “remember” where you were. In side effect heavy code where you don’t always have a lot of idempotent operations, this helps you handle complex happy and unhappy paths to clean up messes and still inform the world of what’s going on (i.e. your Store). They even have a built in message bus for these Sagas to talk to each other with a reasonable amount of determinism. They’re hard to debug, a pain to test, verbose to setup, and a sign you need heavier investment on tackling your [back-end for your front-end](https://samnewman.io/patterns/architectural/bff/) story.

Elm has no side effects. Calling `http.get` doesn’t actually make an HTTP XHR/fetch call; it just returns an Object. While you can do async things with [Task](https://package.elm-lang.org/packages/elm/core/latest/Task), those are typically edge cases. So there is no need for libraries like Thunk or Saga. Whether the action is sync like calculating some data, or async like making an HTTP call, Elm handles all that for you using the same API. You’ll still need to create, at minimum, 2 `Msg`‘s; 1 for initiating the call, and 1 for getting a result back if the HTTP call worked or not.

Both React & Elm still have the same challenge of defining all of your states, and having a UI designer capable of designing for those. Examples include loading screens, success screens, failure screens, no data screens, unauthorized access screens, logged out re-authentication screens, effectively articulating to Product/Business why modals are bad, and API throttling screens.

No one has figured out race conditions.

Error Boundaries
----------------

React has [error boundaries](https://reactjs.org/docs/error-boundaries.html), a way for components to capture an error from children and show a fallback UI vs the whole application exploding. While often an after thought, some teams build in these Action Creators and reducers from the start for easier debugging in production and a better overall user experience.

Elm does not have runtime exceptions, so there is no need for this. However, if you utilize [ports](https://guide.elm-lang.org/interop/ports.html) and talk to JavaScript, you should follow the same pattern in Redux, and create a `Msg` in case the port you’re calling fails “because JavaScript”. While Elm never fails, JavaScript does, and will.

Adding a New Feature
--------------------

When you want to add a new feature to React Redux, you typically go, in order:

1. create a new component(s)
2. add new hooks/action creators
3. update your `mapDispatchToProps`
4. add a new reducer
5. re-run test suite in hopes you didn’t break anything

To add a new feature to Elm, in order:

1. create a new component(s)
2. add a new `Msg` type
3. add that `Msg` type to your component’s click, change, etc
4. update your `update` function to include new `Msg`
5. compiler will break, ensuring when it compiles, your app works again.

That #5 for Elm is huge. Many have learned about it after working with TypeScript for awhile. At first, battling an app that won’t compile all day feels like an exercise in futility. However, they soon realize that is a good thing, and the compiler is helping them a ton, quickly (#inb4denorebuilttscompilerinrust). When it finally does compile, the amount of confidence they have is huge. Unlike TypeScript, Elm guarantees you won’t get exceptions at runtime. Either way, this is a mindset change of expecting the compiler to complain. This eventually leads you to extremely confident massive refactoring of your application without fear.

Updating Big Models
-------------------

React and Elm both suffer from being painful to update large data models.

![](https://jessewarden.com/wp-content/uploads/2020/10/Screen-Shot-2020-10-17-at-2.09.35-PM-939x1024.png)For React, you have a few options. Two examples, just use a lens function like [Lodash’ set](https://lodash.com/docs/4.17.15#set) which supports dynamic, deeply nested paths using 1 line of code… or use [Immer](https://github.com/immerjs/immer).

For Elm, lenses are an anti-pattern because the types ensure you don’t have

```javascript
undefined is not a function
```


…which means everything has to be typed which is awesome… and brutal. I just use [helper functions](https://medium.com/elm-shorts/updating-nested-records-in-elm-15d162e80480).

Testing
-------

For React the only unit tests you need are typically around your reducer functions. If those are solid, then most bugs are caused by your back-end breaking, or changing the JSON contract on you unexpectedly. The minor ones, like misspelling a click handler, are better found through manual & end to end testing vs mountains of jest code. End to end / functional tests using [Cypress](https://www.cypress.io/) can tell you quickly if your app works or not. If you’re not doing pixel perfect designs, then snapshot tests add no value and they don’t often surface what actually broke. The other myriad of JavaScript scope/closure issues are found faster through manual testing or Cypress. For `useEffect`, god speed.

For Elm, while they have unit tests, they don’t add a lot of value unless you’re testing logic since the types solve most issues. Unit tests are poor at validating correctness and race conditions. Typically, strongly typed functional programming languages are ripe for property / fuzz testing; giving your functions a bunch of random inputs with a single test. However, this typically only happens when you’re parsing a lot of user input for forms. Otherwise, the server is typically doing the heavy lifting on those types of things. Instead, I’d focus most of your effort on end to end tests here as well with unhappy paths to surface race conditions.

Conclusions
-----------

React and Elm both have components. In both languages, they’re functions. If you use TypeScript in React, then they’re both typed. Your Action Creators are a `Msg` type in Elm. If you use TypeScript, they’re a simpler [discriminated union](https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#discriminating-unions). In React, you have a Store, which is 1 big Object which represents your applications data model. Through [Event Sourcing](https://www.martinfowler.com/eaaDev/EventSourcing.html), it’s updated over time. In Elm, you have a single Model, and it’s updated over time as well. In React, through a ton of plumbing, your Action Creators are dispatched when you click things to run reducer functions. These pure functions return data to update your store. Elm is similar; clicking things in your view dispatches a `Msg`, and your `update` function is called with this message, allowing you to return a new model. Both require good UI designers to think about all the possible states, and both get good returns on investment in end to end / functional tests. For Elm, you don’t need to worry about error boundaries, or async libraries.