Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
April 25, 2021 08:35 pm GMT

React to Elm Migration Guide

This guide will help you learn and migrate to Elm with assumption you already know the basics of React. The Elm guide is great and will give you a thorough understanding of everything you need to know, in a good order.

This guide is different. Instead, were going to start with the fundamentals of JavaScript & React, and how youd do the equivalent, if any, in Elm. If you already know React, well use those firm groundings so you can feel more comfortable understanding what Elm means when they say something using language and concepts familiar to React developers.

Contents

What is React?

React is a library for ensuring your DOM is in sync with your data. However, one could argue its a framework in that it provides many fundamentals needed to build applications. There is enough features that you can adopt, that it is a sliding scale. Just want JSX and variables? Cool. Want a Context to emulate Redux? Cool. Want to swap out the render for something like Preact? Cool.

Modular to add and swap out parts with large community support to modify it to suit your needs.

Its assumed you can write React in JavaScript. There are basic runtime typings enforced for component properties built into React. If you want something more, TypeScript support has been added as well.

A popular project, create-react-app, rose in popularity because of its ability to handle the compiler toolchain for you. Teams do not have to know about Webpack or JavaScript build targets such as CommonJS, ES6, or ES5. While they dont have to maintain the core, for cyber security findings or build reasons, you/the team will still have to upgrade more than you might want to. Out of the box you get a simple development environment with the ability to save a file and see it live reload. Tests are setup and ready to go. Finally, a production build with all kinds of optimizations are there. Having 3 simple basic commands of start, test, and build give you all you need to build most apps.

While you can utilize npm, yarn is supported for those who want additional features that yarn provides.

Top

What is Elm?

Elm is a strongly typed functional language, compiler, package manager, and framework. You write in the Elm language, and it compiles to JavaScript for use in the browser. The Elm compiler has 2 basic modes of development, and production. It optionally has a REPL if you want to test some basic code. The package manager uses its own website and structure using elm.json, instead of package.json. The framework is what Elm is most known for, and was the inspiration for Redux.

You code in Elm, in the Elm framework, install Elm libraries, and compile using the Elm compiler, into JavaScript. Most learning apps will compile to an HTML page which includes the JavaScript and CSS automatically. For more commonly advanced applications, youll just compile to JavaScript and embed in your own index.html. This often works better when you want to do additional HTML and CSS things to the main HTML file. There is a create-elm-app but it tends to violate the Elm philosophy of not using complex, hard to maintain JavaScript build tool-chains.

JavaScript and Elm Language Types

The following tables compare the basics of JavaScript to Elm.

Top

Literals

JavaScriptElm
33
3.1253.125
"Hello World!""Hello World!"
'Hello World!'cannot use single quotes for strings
'Multiline string.' (backtick, not ')"""Multiline string"""
No distinction between characters and strings.'a'
trueTrue
[1, 2, 3][1, 2, 3]

Top

Objects / Records

JavaScriptElm
{ x: 3, y: 4 }{ x = 3, y = 4 }
point.xpoint.x
point.x = 42{ point | x = 42 }

Top

Functions

JavaScriptElm
function(x, y) { return x + y }\x y -> x + y
Math.max(3, 4)max 3 4
Math.min(1, Math.pow(2, 4))min 1 (2^4)
numbers.map(Math.sqrt)List.map sqrt numbers
points.map( p => p.x )List.map .x points

Top

Control Flow

JavaScriptElm
3 > 2 ? 'cat' : 'dog'if 3 > 2 then "cat" else "dog"
var x = 42; ...let x = 42 in ...
return 42Everything is an expression, no need for return

Top

String

JavaScriptElm
'abc' + '123'"abc" ++ "123"
'abc'.lengthString.length "abc"
'abc'.toUpperCase()String.toUpper "abc"
'abc' + 123"abc" ++ String.fromInt 123

Top

Nulls and Errors

JavaScriptElm
undefinedMaybe.Nothing
nullMaybe.Nothing
42Maybe.Just 42
throw new Error("b00m")Result.Err "b00m"
42Result.Ok 42

Top

JavaScript

Youll often see JavaScript to emulate the above using Optional Chaining.

// has a valueconst person = { age: 42 }const age = person?.age// is undefinedconst person = { }const age = person?.age

Elm

type alias Person = { age : Maybe Int }-- has a valuelet person = Person { age = Just 42 }-- is nothinglet person = Person { age = Nothing }

Function Composition (i.e. Pipelines)

Both languages below parse the following JSON String to get human names in a list.

Top

JavaScript

The JavaScript Pipeline Operator proposal is at stage 1 at the time of this writing, so well use a Promise below.

const isHuman = peep => peep.type === 'Human'const formatName = ({ firstName, lastName }) => `${firstName} ${lastName}`const parseNames = json =>  Promise.resolve(json)  .then( JSON.parse )  .then( peeps => peeps.filter( isHuman ) )  .then( humans => humans.map( formatName ) ) 

Elm

isHuman peep =  peep.type == "Human"formatName {firstName, lastName} =  firstName ++ " " ++ lastNameparseNames json =  parseJSON  |> Result.withDefault []  |> List.filter isHuman  |> List.map formatName

Top

Pattern Matching

JavaScript

The current pattern matching proposal for JavaScript is Stage 1 at the time of this writing.

switch(result.status) {  case "file upload progress":    updateProgressBar(result.amount)  case "file upload failed":    showError(result.error)  case "file upload success":    showSuccess(result.fileName)  default:    showError("Unknown error.")}

Elm

case result.status of  FileUploadProgress amount ->    updateProgressBar amount  FileUploadFailed err ->    showError err  FileUploadSuccess fileName ->    showSuccess filename  _ ->    showError "Unknown error."

Top

Hello World: React

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

Hello World: Elm

type Msg = Bruhtype alias Model = {}update _ model =    modelview _ =    div [] [ h1 [][ text "Hello World!" ] ]main =    Browser.sandbox        { init = (\ () -> {})        , view = view        , update = update        }

Top

DOM Templates

JSX Element

const element = <h1>Hello world!</h1>;

Elm Element

let element = h1 [] [text "Hello World!"]

JSX Dynamic Data

const name = 'Jesse';<h1>Hello {name}</h1>

Elm Dynamic Data

let name = "Jesse"h1 [] [text "Hello " ++ name ]

JSX Functions

const format = ({ first, last }) => `${first} ${last}`;const user = { first: 'Jesse', last: 'Warden' };<h1>Hello {format(user)}</h1>

Elm Functions

format {first, last} = first ++ " " ++ lastuser = { first = "Jesse", last = "Warden" }h1 [] [text (format user) ] 

JSX Image

<img src={user.avatarUrl} />

Elm Image

img [ src user.avatarUrl ] []

JSX Children

const element = (  <div>    <h1>Hello!</h1>    <h2>Good to see you here.</h2>  </div>);

Elm Children

let element =  div [] [    h1 [] [text "Hello!"]    h2 [] [text "Good to see you here."]  ]

Top

Components

React: Define

const Welcome = props => <h1>Hello {props.name}</h1>

Elm: Define

welcome props = h1 [] [text "Hello " ++ props.name]

React: Use

const element = <Welcome name="Sara" />

Elm: Use

let element = welcome { name = "Sara" }

React: Children

const Greeting = ({ name }) => (  <div>    <h1>Hello!</h1>    <h2>Good to see you here, {name}!</h2>  </div>)

Elm: Children

greeting {name} =  div [] [    h1 [] [text "Hello!"]    , h2 [] [text "Good to see you here, " ++ name ++ "!"]  ]

Top

Event Handling

React Event Handler

<button onClick={activateLasers}>  Activate Lasers </button>

Elm Message

button [ onClick ActivateLasers ] [ text "Activate Lasers" ]

React Event Parameter

<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>

Elm Message Parameter

type Msg = DeleteRow Intbutton [ onClick (DeleteRow 23) ] [ text "Delete Row" ]

Top

Event Handling With State

React

class Toggle extends React.Component {  constructor(props) {    super(props);    this.state = {isToggleOn: true};  }  handleClick = () => {    this.setState(state => ({ isToggleOn: !state.isToggleOn }));  }  render = () => (      {this.state.isToggleOn ? 'ON' : 'OFF'}    )  }}

Elm

type alias Model = { isToggleOn : Bool }initialModel = { isToggleOn = True }type Msg = Toggleupdate _ model =  { model | isToggleOn = not model.isToggleOn }toggle model =    div       [ onClick Toggle ]      [ if model.isToggleOn then          text "ON"        else          text "OFF" ]

Top

Conditional Rendering

React

function Greeting(props) {  const isLoggedIn = props.isLoggedIn;  if (isLoggedIn) {    return <UserGreeting />;  }  return <GuestGreeting />;}

Elm

greeting props =  let    isLoggedIn = props.isLoggedIn  in  if isLoggedIn then    userGreeting()  else    guestGreeting()

Top

Lists

React

const numbers = [1, 2, 3, 4, 5];const listItems = numbers.map((number) =>  <li>{number}</li>);

Elm

let numbers = [1, 2, 3, 4, 5]let listItems =  List.map    (\number -> li [] [text (String.fromInt number)])    numbers

Top

Basic List Component

React

function NumberList(props) {  const numbers = props.numbers;  const listItems = numbers.map((number) =>    <li>{number}</li>  );  return (    <ul>{listItems}</ul>  );}const numbers = [1, 2, 3, 4, 5];<NumberList numbers={numbers} />

Elm

numberList props =  let    numbers = props.numbers    listItems =      List.map        (\number -> li [] [text (String.fromInt number)])        numberslet numbers = [1, 2, 3, 4, 5]numberList numbers

Top

Forms: Controlled Component

React

class NameForm extends React.Component {  constructor(props) {    super(props);    this.state = {value: ''};  }  handleChange = event => {    this.setState({value: event.target.value});  }  handleSubmit = event => {    alert('A name was submitted: ' + this.state.value);    event.preventDefault();  }  render() {    return (      <form onSubmit={this.handleSubmit}>        <label>          Name:          <input type="text" value={this.state.value} onChange={this.handleChange} />        </label>        <input type="submit" value="Submit" />      </form>    );  }}

Elm

type Msg = TextChanged String | Submittype alias Model = { value : String }initialModel = { value = "" }update msg model =    case msg of        TextChanged string ->            { model | value = string }        Submit ->            let                _ = Debug.log "A name was submitted: " model.value            in            modelview model =    form [ onSubmit Submit ][        label            []            [ text "Name:"            , input              [type_ "text", value model.value, onInput TextChanged ] []]        , input [type_ "submit", value "Submit"][]    ]

Top

Thinking In

React

Reacts always been about the ease of creating components, then composing those components together into an application. Look at a UI, see the seems in your mind, and decide who will manage the various pieces of state.

  1. Mock
  2. Component Hierarchy
  3. Represent UI State
  4. Determine Where State Lives

1 Mock Data

In React, youll mock the data you get from the potential back-end API or back-end for front-end that youll build. Below, we hard code some mock JSON so our components can show something and we can visually design & code around this data:

[  {category: "Sporting Goods", price: "$49.99", stocked: true, name: "Football"},  {category: "Sporting Goods", price: "$9.99", stocked: true, name: "Baseball"},  {category: "Sporting Goods", price: "$29.99", stocked: false, name: "Basketball"},  {category: "Electronics", price: "$99.99", stocked: true, name: "iPod Touch"},  {category: "Electronics", price: "$399.99", stocked: false, name: "iPhone 5"},  {category: "Electronics", price: "$199.99", stocked: true, name: "Nexus 7"}];

2 Component Hierarchy

Next, youll either create components from that data that youll represent, and see how each is a component with its own ability to represent the data visually and possibly handle user input or do the same to a design comp given to you by a designer. Whether thats the small components in the tree, or the bigger ones who bring it all together; thats up to you.

Typically youll either eyeball the data and the components will start to visualize in your mind, OR youll see the design comp and start to slice the various parts into a component tree in your head.

1. FilterableProductTable (orange): brings all components together

  1. SearchBar (blue): receives all user input
  2. ProductTable (green): displays and filters the data collection based on user input
  3. ProductCategoryRow (turquoise): displays a heading for each category
  4. ProductRow (red): displays a row for each product

3 Represent UI State

Third, youll think strongly about state if you didnt figure out out going through Step 2. Most data can be props, but if a component is controlled, perhaps it may have its own state that would help it interact with other components? Favor props, but use state where you need to encapsulate it into components. Whether using an Object Oriented class based approach, or a Functional one, often components will contain things you feel its best for them to manage internally.

4 Determine Where State Lives

Lastly, identify who owns the source of truth. While many components can have their own internal state, the state of the app is typically owned by one or a select few. The interactions between these components will help you sus out where it should probably live, and how youll manage it (events, Context, Hooks, Redux, etc).

Top

Elm

While many, myself included, wish to immediately jump to building components, Elm encourages thinking hard about your Model first. Elms types allow you to make impossible application states impossible, and simplifying how you represent things. The good news, if you screw this up, the Elm compiler has the best error messages in the industry and allows you to refactor without fear.

  1. Model Data
  2. Component Hierarchy
  3. Model Data Changes
  4. Handle Events

1 Model Data

Step 1 is to model your data using Elms type system. Like React, some will either be dictated like an API, or its something you can customize from a BFF. However, this can be heavily influenced by your Designers comp as well.

type alias Product = {  category : String  , price : String  , stocked : Bool  , name : String }type alias Model = {  products : List Product}initialModel =[  Product {category = "Sporting Goods", price = "$49.99", stocked = True, name = "Football"}  , Product {category = "Sporting Goods", price = "$9.99", stocked = True, name = "Baseball"}  , Product {category = "Sporting Goods", price = "$29.99", stocked = False, name = "Basketball"}  , Product {category = "Electronics", price = "$99.99", stocked = True, name = "iPod Touch"}  , Product {category = "Electronics", price = "$399.99", stocked = False, name = "iPhone 5"}  , Product {category = "Electronics", price = "$199.99", stocked = True, name = "Nexus 7"}]

2 Component Hierarchy

Almost the exact same as React, except there is no state in components; all state is your Model. Your FilterableProductTable, SearchBar, etc. are just functions that often take in the model as the first and only parameter.

3 Model Data Changes

Even if you use Redux in React, you still reserve the right to occasionally keep internal component state. Not so in Elm; all state is in your model. That means your SearchBar (blue) would have a currentFilter : String on your model to capture what the current filter, if any, exists. Youd also have a onlyInStock : Bool for the checkbox. In React, both of those could be:

  • state in the component via this.state
  • state in the component via FilterableProductTable that youd pass up via events
  • state in Redux
  • state in a Hook
  • state in a shared Context

In Elm, there is no question where: its in the model.

4 Model Event Changes

In Elm, you do not need to decide where UI state lives because all data lives in the Model. Instead, you need to decide how to change that data. For simple applications, its much like youd do in Redux: create a Message containing the new data, and write code to change your model based on that message.

type Msg = ToggleOnlyInStock Bool

Now that you have your message, youll dispatch it when the user clicks the checkbox:

label        [ ]        [ input [ type_ "checkbox", onClick (ToggleOnlyInStock not model.onlyInStock) ] []        , text "Only show products in stock"]

Lastly, change the data based on the message:

update msg model =  ...  ToggleOnlyInStock toggle ->    { model | onlyInStock = toggle }

Top

Development

React

Using create-react-app, youll run npm start and your changes + compile errors will be reflected quickly in the open browser window.

For a production build, run npm run build.

Elm

Using elm-live, youll run elm-live and your changes + compile errors will be reflected quickly in the open browser window.

For a production build, run elm make with the --optimize flag. Its recommended you additionally utilize uglifyjs first with compress then again with mangle, or some other compressor + mangler library.

Top

Testing

React

Using create-react-app, youll run npm test which uses Jest internally. If you are dealing with a lot of data on the UI, or using TypeScript, use JSVerify for property tests. For end to end tests, Cypress is a great choice.

Elm

For Elm, unit tests often do not provide value given the compilers correctness. Theyre better expressed using end to end tests and those are more likely to expose your race conditions. If you are dealing with a lot of data on the UI, use elm-test for property tests. While normally for unit-tests, it has fuzzers and shrinkers built in. For end to end tests, Cypress is a great choice.

Top

Routing

React

While there are a variety of choices, react-router is one many settle on.

function Home() {  return <h2>Home</h2>;}function About() {  return <h2>About</h2>;}function Users() {  return <h2>Users</h2>;}function App() {  return (    <Router>      <div>        <nav>          <ul>            <li>              <Link to="/">Home</Link>            </li>            <li>              <Link to="/about">About</Link>            </li>            <li>              <Link to="/users">Users</Link>            </li>          </ul>        </nav>      </div>    </Router>  )}

Elm

Elm has routing built-in using the Browser library.

home =  h2 [] [ text "Home" ]about =  h2 [] [ text "About" ]users =  h2 [] [ text "Users" ]app =  div [] [    nav [] [      ul [] [        li [] [          a [ href "/home" ] [ text "Home" ]        ]        , li [] [          a [ href "/about" ] [ text "About" ]        ]        , li [] [          a [ href "/users" ] [ text "Users" ]        ]      ]    ]  ]

Top

Error Boundaries

React

In React, youll build a component, or set of components, to wrap common error areas so in case a volatile part of the UI throws, you can handle it gracefully in the UI. First create a basic wrapper component:

class ErrorBoundary extends React.Component {  constructor(props) {    super(props);    this.state = { hasError: false };  }  static getDerivedStateFromError(error) {    // Update state so the next render will show the fallback UI.    return { hasError: true };  }  componentDidCatch(error, errorInfo) {    // You can also log the error to an error reporting service    logErrorToMyService(error, errorInfo);  }  render() {    if (this.state.hasError) {      // You can render any custom fallback UI      return <h1>Something went wrong.</h1>;    }    return this.props.children;   }}

Once youve got your component with logging and a fallback UI, you just wrap the dangerous components:

<ErrorBoundary>  <MyWidget /></ErrorBoundary>

Elm

Elm does not have runtime errors (caveat: port dangers in section down below). The compiler will ensure that all possible errors are handled. This means you either model those error states in your model, ignore them with blank strings, or design different UIs for those states.

Data not there? You must handle it:

case dataMaybe of  Just data ->    addProduct data  Nothing ->    -- Your UI or data must compensate somehow here.    -- For now we just return all the products unchanged    model.products

HTTP operation you need to work fail? You must handle it:

case result of  Error err ->    { model | result = ProductSaveFailed err }  Ok data ->    { mdoel | result = ProductSaveSuccess data }-- in UIcase result of  ProductSaveFailed err ->    errorViewAndRetry err  ProductSaveSuccess _ ->    goToProductView

Top

HTTP

React

class Weather extends React.Component {  constructor(props) {    super(props);    this.state = { temperature: undefined, loading: true };  }  componentDidMount = () => {    this.setState({ loading: true })    fetch("server.com/weather/temperature")    .then( response => response.json() )    .then(        ({ temperature }) => {         this.setState({ temperature, loading: false, isError: false }) )      }    )    .catch(      error => {        this.setState({ loading: false, isError: true, error: error.message })      }    )  }  render() {    if(this.state.loading) {      return <p>Loading...</p>    } else if(this.state.isError === false) {      return <p>Temperature: {this.state.temperature}</p>    } else {      return <p>Error: {this.state.error}</p>    }  }}

Elm

type Msg = LoadWeather | GotWeather (Result Http.Error String)type Model    = Loading    | Success String    | Failure Http.Errorinit : () -> (Model, Cmd Msg)init _ =  ( Loading  , loadTemperature  )loadTemperature =    Http.get      { url = "server.com/weather/temperature"      , expect = Http.expectJson GotWeather temperatureDecoder      }temperatureDecoder =  field "temperature" stringupdate msg model =    case msg of        LoadWeather ->            (Loading, loadTemperature)        GotWeather result ->            case result of                Err err ->                    ( Failure err, Cmd.none )                Ok temperature ->                    ( Success temperature, Cmd.none )view model =    case model of        Loading ->            p [][text "Loading..."]        Success temperature ->            p [][text ("Temperature: " ++ temperature) ]        Failure _ ->            p [][text "Failed to load temperature."]

Top

State Management

Redux

// Action Creatorconst addTodo = text => ({ type: 'ADD_TODO', text })// Dispatchconst goSwimming = () => store.dispatch(addTodo('Go Swimming.'))// trigger from button<button onClick={goSwimming}>Add</button>// update modelconst todos = (state = [], action) => {  switch (action.type) {    case 'ADD_TODO':      return state.concat([{ text: action.text, completed: false }])    default:      return state  }}

Elm

-- Type for Todotype alias Todo = { text : String, completed: Bool }-- Messagetype Msg = AddTodo String-- trigger from buttonbutton [ onClick (AddTodo "Go Swimming.")] [ text "Add" ]-- update modelupdate msg model =  case msg of    AddTodo text ->      { model | todos = List.append model.todos [Todo text, False] }    ...

Top


Original Link: https://dev.to/jesterxl/react-to-elm-migration-guide-30np

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To