null > 0; // false
null == 0; // false
null >= 0; // true
import React, { useState } from "react";
export const Form = () => {
const [name, setName] = useState("");
const [password, setPassword] = useState("");
const onNameChange = e => {
setName(e.target.value);
};
const onPasswordChange = e => {
setPassword(e.target.value);
};
return (
<form>
<input
type="text"
placeholder="Name"
value={name}
onChange={onNameChange}
/>
<input
type="text"
placeholder="Password"
value={password}
onChange={onPasswordChange}
/>
<button type="submit" onSubmit={handleSubmit}>
Submit
</button>
</form>
);
};
[@react.component]
let make = () => {
let (name, setName) = React.useState(() => "");
let (password, setPassword) = React.useState(() => "");
let onNameChange = (e: ReactEvent.Form.t): unit => {
let value = e->ReactEvent.Form.target##value;
setName(value);
};
let onPasswordChange = (e: ReactEvent.Form.t): unit => {
let value = e->ReactEvent.Form.target##value;
setPassword(value);
};
<form>
<input
type_="text"
name="name"
value=name
onChange=onNameChange
placeholder="Name"
/>
<input
type_="password"
name="name"
value=password
onChange=onPasswordChange
placeholder="Password"
/>
<button type_="submit"> {React.string("Submit")} </button>
</form>;
};
module Form where
import Prelude
import Data.Maybe (fromMaybe)
import Effect (Effect)
import React.Basic.DOM as R
import React.Basic.DOM.Events (targetValue)
import React.Basic.Events (handler)
import React.Basic.Hooks (ReactComponent, component, useState, (/\))
import React.Basic.Hooks as React
form :: Effect (ReactComponent {})
form = do
component "form" \_ -> React.do
{ name } /\ setName <- useState { name: "" }
{ password } /\ setPassword <- useState { password: "" }
pure
$ R.form_
[ R.input
{ onChange:
handler targetValue \value ->
setName \_ -> { name: fromMaybe "" value }
, value: name
, placeholder: "Name"
}
, R.input
{ onChange:
handler targetValue \value ->
setPassword \_ -> { password: fromMaybe "" value }
, value: password
, placeholder: "Password"
}
, R.button
{ type: "submit"
, children: [ R.text "Submit" ]
}
]
import * as React from 'react';
export const Form: React.FC = () => {
const [name, setName] = React.useState('');
const [password, setPassword] = React.useState('');
const onNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setName(e.target.value);
};
const onPasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setPassword(e.target.value);
};
return (
<form>
<input
type="text"
value={name}
onChange={onNameChange}
placeholder="Name"
/>
<input
type="password"
value={password}
onChange={onPasswordChange}
placeholder="Password"
/>
<button type="submit">Submit</button>
</form>
);
};
import Browser
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onInput)
main =
Browser.sandbox { init = init, update = update, view = view }
-- MODEL
type alias Model =
{ name : String
, password : String
}
init : Model
init =
Model "" ""
-- UPDATE
type Msg
= Name String
| Password String
update : Msg -> Model -> Model
update msg model =
case msg of
Name name ->
{ model | name = name }
Password password ->
{ model | password = password }
-- VIEW
view : Model -> Html Msg
view model =
Html.form []
[ viewInput "text" "Name" model.name Name
, viewInput "password" "Password" model.password Password
, button [] [ text "Submit" ]
]
viewInput : String -> String -> String -> (String -> msg) -> Html msg
viewInput t p v toMsg =
input [ type_ t, placeholder p, value v, onInput toMsg ] []
ReasonML
3 steps:
npm install --save-dev bs-platform reason-react
{
"name": "your-project-name",
"reason": {
"react-jsx": 3
},
"sources": [
{
"dir": "src",
"subdirs": true
}
],
"suffix": ".bs.js",
"bs-dependencies": [
"reason-react"
],
"refmt": 3
}
PureScript
3 steps:
yarn global add purescript spago
yarn add -D purs-loader
spago init
spago install purescript-react-basic
TypeScript
6 steps:
yarn add -D typescript @babel/preset-typescript fork-ts-checker-webpack-plugin
{
"compilerOptions": {
"lib": ["es6", "dom", "es2017"],
"checkJs": true,
"allowJs": true,
"jsx": "react",
"allowSyntheticDefaultImports": true,
"strict": true,
"esModuleInterop": true,
"noEmitOnError": false
},
"exclude": ["node_modules"],
"include": ["src/**/*"]
}
Elm
3 steps:
yarn add -D react-elm-components elm-webpack-loader
{
"version": "0.0.1",
"type": "application",
"summary": "Elm <> Console",
"license": "Apache",
"source-directories": ["src/elm"],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"elm/browser": "1.0.0",
"elm/core": "1.0.0",
"elm/html": "1.0.0",
"elm/json": "1.0.0"
},
"indirect": {
"elm/time": "1.0.0",
"elm/url": "1.0.0",
"elm/virtual-dom": "1.0.0"
}
},
"test-dependencies": {
"direct": {},
"indirect": {}
}
}
Elm installed globally
Summary
module KnowMoreLink = {
[@bs.module "./KnowMoreLink.js"]
[@react.component]
external make: (
~href: string
~text: string = ?
) => React.element = "default";
};
ReasonML
JavaScript in ReasonML
BuckleScript bindings
ReasonML
ReasonML in JavaScript
import { make as getUser } from './User/index.bs';
const user = getUser();
PureScript
JavaScript in PureScript
exports.unsafeHead = function(arr) {
if (arr.length) {
return arr[0];
} else {
throw new Error('empty array');
}
};
foreign import unsafeHead :: forall a. Array a -> a
Foreign import declaration
PureScript
PureScript in JavaScript
import { button as Button } from './Button/Button.purs';
<Button>Submit</Button>
TypeScript
JavaScript in TypeScript
// Dropdown.d.ts
type Props = {
options: Array<{ content: string }>;
dismiss(): void;
position: 'bottom' | 'right';
};
declare const Dropdown: React.FC<Props>;
export default Dropdown;
allowJs: true
TypeScript
TypeScript in JavaScript
Elm
"JavaScript in Elm"
var app = Elm.Main.init({
node: document.getElementById('elm'),
flags: locale
});
1. Flags
Elm
"JavaScript in Elm"
2. Ports
var app = Elm.Main.init({
node: document.getElementById('elm')
});
app.ports.cache.subscribe(function(data) {
localStorage.setItem('cache', JSON.stringify(data));
});
app.ports.activeUsers.send(activeUsers);
Elm
Elm in JavaScript
import Button from './elm/Button.elm';
import Elm from 'react-elm-components';
<Elm src={Button.Elm.Main} />
Mental model
But... dynamic/static typing interop is unsound in all of these languages
Ensuring soundness in dynamic/static typing interop comes with a runtime cost
TypeScript
Hasura Console is a big, opensource project
Hasura Console is a big, opensource project
Low migration cost allows to keep velocity high.
Hasura Console is a big, opensource project
Low setup cost allows to keep velocity high.
Smallest difference between languages won't scare contributors.
What will you choose?
We chose TypeScript.