DEV Community

Cover image for Elm for React developers
Ryan Haskell-Glatz
Ryan Haskell-Glatz

Posted on

Elm for React developers

Hi there! πŸ‘‹

When I was first learning Elm, it was helpful for me to see side-by-side examples alongside ones from the JavaScript frameworks I was already familiar with.

Recently, the React community released a beautiful guide at React.dev to help beginners learn the framework. This post uses the code examples from their new "Quick Start" guide, to help you leverage your React experience to learn more about Elm!

🚨 Note: The goal of this post is not to compare React to Elm, in an attempt to make one look "better" than the other. Both options are great, so build stuff with whichever you prefer!

Table of contents

Components

Demo of basic react app

When writing React components, we use JSX– a hybrid of HTML and JavaScript syntax:

function MyButton() {
  return (
    <button>
      I'm a button
    </button>
  );
}

function MyApp() {
  return (
    <div>
      <h1>Welcome to my app</h1>
      <MyButton />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Elm's syntax doesn't look like HTML, but the concepts are mostly the same. We can define functions like myButton or myApp, and reuse them alongside the normal HTML tags imported on line 1:

import Html exposing (..)

myButton =
    button [] [ text "I'm a button" ]

myApp =
    div []
        [ h1 [] [ text "Welcome to my app" ]
        , myButton
        ]
Enter fullscreen mode Exit fullscreen mode

πŸ”— Demo: Components

Displaying data

Demo of the Hedy Lamarr app

Displaying data in JSX is easy– we can use the {} characters to "escape" from our HTML and provide values from JavaScript:

const user = {
  name: 'Hedy Lamarr',
  imageUrl: 'https://i.imgur.com/yXOvdOSs.jpg',
  imageSize: 90,
};

export default function Profile() {
  return (
    <>
      <h1>{user.name}</h1>
      <img
        className="avatar"
        src={user.imageUrl}
        alt={'Photo of ' + user.name}
        style={{
          width: user.imageSize,
          height: user.imageSize
        }}
      />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Because Elm doesn't use HTML syntax, you won't need any escape characters to display data. You can reference those values as if you were writing normal JavaScript code:

import Html exposing (..)
import Html.Attributes exposing (..)

user =
    { name = "Hedy Lamarr"
    , imageUrl = "https://i.imgur.com/yXOvdOSs.jpg"
    , imageSize = 90
    }

profile =
    [ h1 [] [ text user.name ]
    , img
        [ class "avatar"
        , src user.imageUrl
        , alt ("Photo of " ++ user.name)
        , style "width" (String.fromInt user.imageSize)
        , style "height" (String.fromInt user.imageSize)
        ]
        []
    ]
Enter fullscreen mode Exit fullscreen mode

πŸ”— Demo: Displaying data

Conditional rendering

Conditional rendering demo

In React, you can use JavaScripts if/else syntax or the ternary operator to conditionally render your content:

let content;
if (isLoggedIn) {
  content = <AdminPanel />;
} else {
  content = <LoginForm />;
}

// USING TERNARY INSTEAD:
// let content = (isLoggedIn) ? <AdminPanel /> : <LoginForm />
Enter fullscreen mode Exit fullscreen mode

In Elm, the if/else expression always returns its value. That means content will be set, depending on isLoggedIn.

For me, it was helpful to think of Elm's if/else like it was ternary, but with the readability of JavaScript's if statement:

content =
    if isLoggedIn then
        adminPanel

    else
        loginForm
Enter fullscreen mode Exit fullscreen mode

πŸ”— Demo: Conditional rendering

Rendering lists

Rendering lists demo

All arrays in JavaScript support the .map function, which allows React developers to return lists of components like these list items:

const products = [
  { title: 'Cabbage', isFruit: false, id: 1 },
  { title: 'Garlic', isFruit: false, id: 2 },
  { title: 'Apple', isFruit: true, id: 3 },
];

export default function ShoppingList() {
  const listItems = products.map(product =>
    <li
      key={product.id}
      style={{
        color: product.isFruit ? 'magenta' : 'darkgreen'
      }}
    >
      {product.title}
    </li>
  );

  return (
    <ul>{listItems}</ul>
  );
}
Enter fullscreen mode Exit fullscreen mode

In Elm, values don't have methods, so we use List.map with the function and array we are looping over:

products =
    [ { title = "Cabbage", isFruit = False, id = 1 }
    , { title = "Garlic", isFruit = False, id = 2 }
    , { title = "Apple", isFruit = True, id = 3 }
    ]

shoppingList =
    let
        listItems =
            List.map 
                (\product ->
                    li
                      [ if product.isFruit then 
                          style "color" "magenta"

                        else
                          style "color" "darkgreen"
                      ]
                      [ text product.title ]
                )
                products
    in
    ul [] listItems
Enter fullscreen mode Exit fullscreen mode

πŸ”— Demo: Rendering lists

Note: Elm functions can use let/in to have "locally-scoped" variables like listItems. When we nested listItems inside of the let/in block, it meant that only the code in shoppingList can access the listItems variable. This is unlike products, which is available to any function in our Elm file.

Responding to events

Responding to events demo

In React, you'll use "hooks" like useState to track application state. Here's an example of how to do this with a counter and a button:

function MyButton() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <button onClick={handleClick}>
      Clicked {count} times
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

All Elm applications use the same init/update/view pattern to keep track of application state.

In our example:

  • init sets the initial value of the counter
  • update defines how our count changes from user events
  • view defines how to render our HTML, based on the latest count
import Html exposing (..)
import Html.Events exposing (..)

-- INIT

init = 0

-- UPDATE

type Msg = HandleClick

update msg model =
    case msg of
        HandleClick ->
            model + 1

-- VIEW

view model =
    button 
        [ onClick HandleClick ]
        [ text ("Clicked " ++ String.fromInt model ++ " times")
        ]
Enter fullscreen mode Exit fullscreen mode

πŸ”— Demo: Responding to events

Sharing data between components

Sharing data between components demo

In React, our state is defined within our component functions. If we want to share it, we pass "props" like count or handleClick into child components like <MyButton>:

function MyApp() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <div>
      <h1>Counters that update together</h1>
      <MyButton count={count} onClick={handleClick} />
      <MyButton count={count} onClick={handleClick} />
    </div>
  );
}

function MyButton({ count, onClick }) {
  return (
    <button onClick={onClick}>
      Clicked {count} times
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

In Elm apps, we always use the init/update/view pattern. This means we can pass our count and HandleClick event into our myButton function as props from our top-level view function:

import Html exposing (..)
import Html.Events exposing (..)

-- INIT

init = 0

-- UPDATE

type Msg = HandleClick

update msg model =
    case msg of
        HandleClick ->
            model + 1

-- VIEW

view model =
    div []
      [ h1 [] [ text "Counters that update together" ] 
      , myButton { count = model, onClick = HandleClick }
      , myButton { count = model, onClick = HandleClick }
      ]

myButton props =
    button 
        [ onClick props.onClick ]
        [ text ("Clicked " ++ String.fromInt props.count ++ " times")
        ]
Enter fullscreen mode Exit fullscreen mode

πŸ”— Demo: Sharing data between components

That's all I've got!

I hope this tiny guide was helpful, and that you learned something new today. If you want to learn more about Elm, you should check out the official guide or say hello in the Elm Slack #beginners channel

Have a great day! πŸ‘‹

Latest comments (5)

Collapse
 
corners2wall profile image
Corners to wall

For me react syntax is more readable

Collapse
 
rhg profile image
Ryan Haskell-Glatz

It definitely was for me, too!

When I got started with coding, it was with Java in high school πŸŽ’

After that it was C++, then C#, and eventually JavaScript– where I found my passion for frontend web development.

All those languages looked pretty similar to each other, so I visually preferred languages with the brackets, parentheses, and semicolons.

When I saw Elm for the first time, I almost gave up– it looked like a weirder version of Python to me πŸ˜…

You're definitely not alone in feeling this way– React's syntax is very readable, and that makes it a lot easier to get started with!

Collapse
 
sethcalebweeks profile image
Caleb Weeks

It would be cool to make a JSX to Elm compiler. Elm already has a single file component structure, but maybe introduce another extension that lets you define your view in JSX. I might look into doing this as a fun side project.

Thread Thread
 
sethcalebweeks profile image
Caleb Weeks

Good looking post to get started:
blog.bitsrc.io/demystifying-jsx-bu...

Collapse
 
wolfadex profile image
Wolfgang Schuster

You hinted at keyed elements in Rendering lists example. This is also possible, and sometimes just as practical, in Elm with

products =
    [ { title = "Cabbage", isFruit = False, id = 1 }
    , { title = "Garlic", isFruit = False, id = 2 }
    , { title = "Apple", isFruit = True, id = 3 }
    ]

shoppingList =
    let
        listItems =
            List.map 
                (\product ->
                    ( String.fromInt product.id 
                    , li
                        [ if product.isFruit then 
                            style "color" "magenta"

                          else
                            style "color" "darkgreen"
                        ]
                        [ text product.title ]
                    )
                )
                products
    in
    Html.Keyed.ul [] listItems

Enter fullscreen mode Exit fullscreen mode