loading...

Sprinkle some Elm in your React

andreligne profile image André Originally published at andreligne.io on ・5 min read

Sprinkle some Elm in your React

After being out of the Elm world for about a year, I decided this weekend to try it out again. I created a new application (using create-elm-app) and got going. After a few minutes, the joy of writing Elm was back. The helpful error messages, the flexibility of describing data in the type system - it was fantastic!

In another side-project I've been working on for a few months, we are using React. Even though React is an excellent framework for building web applications, my fingers started to itch for writing Elm again.

Then it occurred to me - why can't we write an Elm module for a small set of a screen and embed that in a React component?

So that's what this post is about!


To get started, we need a project set up using webpack capable of compiling both React and Elm files. I'll leave this as an exercise for the reader.

This is the contents of the Main.elm file that we'll use to try and figure this one out.

module Main exposing (..)

import Browser
import Html exposing (Html, text)

main : Program () Model Msg
main =
    Browser.element
        { init = init
        , view = view
        , update = update
        , subscriptions = subscriptions
        }

type alias Model =
    {}

init : () -> ( Model, Cmd Msg )
init flags =
    ( Model
    , Cmd.none
    )

type Msg
    = Noop

update : Msg -> Model -> ( Model, Cmd Msg )
update _ model =
    ( model, Cmd.none )

view : Model -> Html Msg
view model =
    Html.div [] [text "Hello from Elm!"]

subscriptions : Model -> Sub Msg
subscriptions _ =
    Sub.none

This doesn't do anything interesting since it only prints out the string "Hello from Elm!", but it's good enough to try and get it running inside of a React component.

Sprinkle some Elm in your React
Screenshot of the Elm application we'll wrap

How does an Elm application initialize?

When you create any Single Page Application (SPA), you usually have some entry point Javascript that imports the SPA's main module and mounts it on a specific DOM node on the page.

To take a closer look at this, we can open the generated index.html file.

import { Elm } from '../Main';

document.addEventListener('DOMContentLoaded', () => {
  const target = document.createElement('div');
  document.body.appendChild(target);

  Elm.Main.init({ node: target });
});
Example hello_elm.js from a new project
  • First, we import the Elm application from the Main.elm file
  • On the DOMContentLoaded event, we create a new DOM element for a div and append it to the body
  • We then initialize the Elm application to run inside the new div

The main takeaway here is that we will need a DOM node to run our Elm application inside.

Creating the React component

Now that we understand how the Elm application gets initialized, we can start thinking about designing our React component to host it.

Since the only thing we need to initialize the Elm application is a DOM node, let's create a component that renders a div we can use as our target.

import React from 'react';

export default function ElmComponent() {
  return <div />;
}

To get the reference of that div, we can use the useRef hook from react to get a reference to the DOM node for the entire lifecycle of the component. The useRef function takes an initial value

import React, { useRef } from 'react';

export default function ElmComponent() {
  const target = useRef();

  return <div ref={target} />;
}

Now that we know where to put the application, we can import our Elm module and use the useEffect hook to initialize it when the component first mounts to the DOM.

import React, { useRef } from 'react';
import { Elm } from '../Main';

export default function ElmComponent() {
  const target = useRef();

  useEffect(() => Elm.Main.init({ node: target.current });

  return (
    <>
      <h1>Hello from React!</h1>
      <div ref={target} />
    <>
  );
}

Now when we render our ElmComponent, our Elm application will run inside our React application.

Sprinkle some Elm in your React
The final result

How can we pass data from React?

While embedding the Elm application in an already existing React application, there's a good chance you want to send along some data from the React app to the Elm app. This could be anything from authentication tokens for making HTTP requests to a server to the current user.

To do this, we can change the type signature of our main function in the Elm program to signal that we're expecting the program to receive initial data when it starts up. We also then to add to the data we want to hold in the program to our Model type.

For passing an authentication token from React into the Elm program, so that we can use it to make HTTP requests, we could change our module to something like this.

import Browser
import Html exposing (Html, text)

main : Program String Model Msg
main =
    Browser.element
        { init = init
        , view = view
        , update = update
        , subscriptions = subscriptions
        }

type alias Model =
    { authToken : String }

init : String -> ( Model, Cmd Msg )
init authToken =
    ( Model authToken
    , Cmd.none
    )

type Msg
    = Noop

update : Msg -> Model -> ( Model, Cmd Msg )
update _ model =
    ( model, Cmd.none )

view : Model -> Html Msg
view model =
    Html.div [] [text <| "Token: " ++ model.authToken]

subscriptions : Model -> Sub Msg
subscriptions _ =
    Sub.none

The differences in our new Elm program is:

  • Model is constructed with an authToken : String value
  • The type signature of main now specifies the type of the flags passed to it
  • init also has an updated type signature an build the initial Model with the value from the flags

If we now render the ElmComponent and pass along a string as the flags prop, the Elm program will store that inside the initial model so that we can use it later. Let's run the new program.

import React, { useRef } from 'react';
import { Elm } from '../Main';

export default function ElmComponent() {
  const target = useRef();

  useEffect(() => Elm.Main.init({
    node: target.current,
    flags: "my-very-secret-token"
  });

  return <div ref={target} />;
}

Sprinkle some Elm in your React
Our rendered Elm program with arguments passed from React.

You can also pass along more complex data to your Elm program using flags, such as objects, tuples, arrays and so on. The Elm guide has great documentation if that is something you want to read more about!

Flags · An Introduction to Elm

In summary

By using this little building block in your React application, you can start introducing Elm into your codebase without rewriting the application.

This can help you try if you like it enough in a low-risk situation and later decide if you want to expand your usage of it throughout the application. Otherwise, you only have a small portion of Elm running and can easily convert it back to React.

But since Elm is fantastic, that'll probably never happen. 😁

Discussion

pic
Editor guide
 

Hello,
Try the Meiosis pattern for React
Regards

 

The main reason for me to use Elm is the type system rather than the pattern itself :)

What is your experience using that framework? Do you find it easier to work with than using something like Redux?

 

Hy André,
My suggestion does not apply if you use ELM.
ELM is a great framework, it suffices in itself.
Look at Meiosis only if you have to work with React

Regards

 

What do you think about Hyperapp (Elm as a JS framework) or using xstate in React?

 

I haven’t used any of those, so I can’t say too much about them specifically.

To me, the biggest value proposition of using Elm is the type system and the feedback loop while having a great compiler. 😄