There are ways to achieve many of the features of Elm in a React app, but in practice it is much harder earned.
When escape hatches are present, we as humans will use them. When code discipline is enforced by the language, our mental load is decreased.
Elm enables us to work with complexity without losing sleep.
Gradual adoption with Parcel
The Elm creator Evan Czaplicki has written some guidelines on gradual introduction if you are serious about trying Elm in production.
In this guide, I will use the Parcel bundler instead of Create React App (Webpack). There are already a few Webpack oriented tutorials out there (links at the bottom).
I also want to show how to do it with function components and TypeScript.
Parcel has built-in support for Elm, so there is not much configuration to it.
Implementing Elm in React is actually deceptively simple. As long as you know how, of course.
NOTE: I won't explain much of the Elm stuff. It is best covered in the official introduction.
Although this is an advanced topic, this tutorial can be useful for seasoned React developers wanting to get a hands-on feel of Elm.
Let's get started!
Clone the starter repo. It's basically a Parcel starter with React and TypeScript.
Create a folder in
src named Elm and create the file
We will start with some hard-coded markup.
Insert this into the
To avoid TypeScript errors, let's also declare the module in a new file named
Insert this code into
Copy-pasting this is fine. We will soon automatically generate this file, so you won't need to modify it manually.
Inject Elm into the React App
Go to the
index.tsx file and import the Elm module:
ElmComponent to look like this:
yarn start. Parcel will automatically install the Elm dependencies. The app will probably fail.
You will now see a new file in your project root named
Rewrite the source-directories property in
elm.json into this:
Press Ctrl+c if the app is running, run
yarn start again and check http://localhost:1234.
Your app is gradually improving, just by having some Elm in it 😄
Only thing is, the Elm part is just a simple type-safe Html element, but there is no state in the app below.
Let's fix that.
Make Elm own the state
I will make Elm own the state and be the source of truth.
To me, it makes most sense to have Elm control the state and pass it to React. There could of course be use-cases where it's necessary to control the state with React at first.
Make Elm stateful
To make the Elm component more interactive, we need to add a bit more stuff to the
To walk through the basics, we turn the main component into a Browser.element. The html is moved from main into
mainis the entry point of the app
initsets initial state (of type Model). Also runs initial commands/side-effects (if needed)
updateupdates the model, it's like a reducer, but better
viewnow takes in the model, displays it and is able to send messages (Msg) to to update
Modelis the type definition for the state model. In this case, it's just an integer. Normally it's a record with lots of data.
Msgis a collection of messages to dispatch to update, comparable to Redux actions
Now, you have two components with state working independently of each other.
Also notice on the bottom right corner, you have gained a powerful time-traveling state debugger for the elm state.
The finish line is closing in. Let's hook the components together.
Establish communication lines in Elm
First, we will put the word
port in front of the top module declaration like this:
Then we will add a port command that we will send to React, and a
Flags type for defining the initial state coming from React.
We also need to tell the
And we take the flags in init and set them as the initial state for the Elm app.
Last thing in the Elm code is to replace
Cmd.none with the
updateCountInReact command in the update function:
I'm keeping the
model + step logic duplicated, just to keep the example simple, but you could of course make it into a function.
The Elm logic is finished, but the app will not work until we have adjusted the React part.
Establish communication lines in React
When using TypeScript, we need to update the type definitions for the flags and ports we just made. Doing it manually is just painful.
package.json, just add this script. There is no need to install it:
index.d.ts in the
Main folder is now updated with the types from the Elm ports 🎉. Just run this command every time you need to update something between Elm and TypeScript.
Now let's fix the typescript error that appeared in
index.tsx by setting flags to the initial value from React, namely the
count value passed from props.
TypeScript is satisfied. And now lets's add another
useEffect in the
ElmComponent, so the component looks like this.
ReactComponent, we can remove the update logic, as Elm has taken over the state management.
The result is in
Now you control the state rendered in both React and Elm from the Elm App.
Exactly how you do it in your own project might vary. A viable path is to start using Elm as the state manager and React as the view renderer. Then you learn lots of the good parts of Elm first.
When Elm owns all state, you can gradually take over the React rendering by expanding the
view function and replace the React views.
You could also make an an incoming message port from React to Elm and pass string messages to Elm which then updates the state. I plan on making another post on this subject in a couple of weeks.