Margarita

Krutikova

Kyrylo

Yakymenko

Agenda

  • What is Reason?
  • Why should we care?
  • Language features
  • The new ReasonReact hooks API
  • ​Summary

a functional programming language

React bindings for ReasonML

compiles ReasonML to JavaScript

syntactic extension of

Allows writing React applications in ReasonML

"Babel" for ReasonML

Why?

  • Statically typed, functional and pragmatic
    best of two worlds

Constraints

Flexibility

Guarantees

"Sweet Spot of Pragmatism"

Why?

  • Statically typed, functional and pragmatic
    best of two worlds
  • Powered by Facebook (used in production)

Why?

  • Statically typed, functional and pragmatic
    best of two worlds
  • Powered by Facebook
  • Fun fact: Jordan Walke, the creator of React is now working on Reason

Why?

  • Statically typed, functional and pragmatic
    best of two worlds
  • Powered by Facebook
  • Fun fact: Jordan Walke, the creator of React is now working on Reason
  • Expressive type system, strong type inference
  • Extremely fast builds

Why?

  • Statically typed, functional and pragmatic
    best of two worlds
  • Powered by Facebook
  • Fun fact: Jordan Walke, the creator of React is now working on Reason
  • Expressive type system, strong type inference
  • Extremely fast builds
  • Access to the whole JS ecosystem (npm)
  • Incremental adoption possible

Syntax

- reminds of Javascript 









    let multiply = (a, b) => a * b;


    let welcome = name => "Welcome, " ++ name;
        

    let veggies = ["pumpkin", "kale", "broccoli"];

All JavaScript

developers in the world

JavaScript developers willing to learn the OCaml syntax

The cool stuff

  • Variants + pattern matching






      type response = 
       | Fetching
       | Error(string)
       | Success(user);

Variant



      switch (response) {
       | Fetching => <Spinner />
       | Error(err) => <Error err />
       | Success(user) => <MyPages user />
      }
     
     
     


                type Response = {
                  error: string | null,
                  user: User | null,
                  isLoading: boolean
                }


                if (response.loading) {
                 ...
                }
                if (response.error) {
                 ...
                }
                if (response.user) {
                  ...
                }

Typescript

Pattern Matching (JS)

switch (action.type) {

  case "FETCHING":
    return "Fetching...";
    
  case "ERROR":
    if (action.errorCode === 401) {
      return "Unauthorized!";
    }
    if (action.errorCode === 404) {
      return "Not found!";
    }
    return "Unknown error...";
    
  case "SUCCESS":
    return action.data;
}

Pattern Matching (Reason)

switch (action) {
  | Fetching => "Fetching..."
  | Error(401) => "Unauthorized!"
  | Error(404) => "Not found!"
  | Error(_) => "Unknown error..."
  | Success(data) => data
}

Pattern Matching (routing)

if (isLoggedIn) {
  if (isLoggedInAsAdmin) {
    return "Access denied to admin content";
  } else {
    if (subRoute.length === 2 && subRoute[0] === "results") {
      const playerName = subRoute[1];
      return `Results for ${playerName}`;
    } else if (subRoute.length === 1 && subRoute[0] === "settings") {
      return "Settings";
    }
  }
} else {
  return "Log In: ...";
}

Pattern Matching (routing)

if (!isLoggedIn) {
  return "Log In: ...";
}

if (isLoggedIn && !isLoggedInAsAdmin) {
  return "Access denied to admin content";
}

if (subRoute.length === 2 && subRoute[0] === "results") {
  const playerName = subRoute[1];
  return `Results for ${playerName}`;
}

if (subRoute.length === 1 && subRoute[0] === "settings") {
  return "Settings";
}

Pattern Matching (routing)

switch (isLoggedIn, isLoggedInAsAdmin, subRoute) {
| (true, true, ["results", playerName]) =>
  "Results for " ++ playerName
| (true, true, ["settings"])
| (true, true, _) => "Settings"
| (true, false, _) => "Access denied to admin content"
| (false, _, _) => "Log in: ..."
};

The cool stuff

  • Variants + pattern matching

  • Option

Option


type option('a) = None | Some('a)
  • No null / undefined

  • Safe alternative to concept of nonexistent value

let maybeInt = Some(45);

let maybeUser = Some({ name: "John" });

The cool stuff

  • Variants + pattern matching

  • Option

  • Records

Records

  • Fixed fields

  • Immutable

  • Very fast

  • Compile to arrays 🤯

Similar to JS objects, but

NOTE: Not since BuckleScript 7

This simplifies interop with JS/TS code

The cool stuff

  • Variants + pattern matching

  • Option

  • Records

  • Module system

  • Built-in formater

Automatic code formatting

JS/TS: Prettier

  • prettier-vscode
  • prettier-eslint
  • eslint-plugin-prettier
  • eslint-config-prettier
  • (tslint-config-prettier)
  • .prettierrc
  • .eslintrc
  • settings.json -> prettier settings

reason-language-server

+

refmt

ReasonML

ReasonReact

  • Reason bindings for React
  • Allows writing React components to whole React applications in ReasonML
  • Supports React hooks as of v0.7.0!
  • Future of React? 🤔 

WE WANT CODE!

Start export

Started

Failed 😭

Poll for progress

Network error

Earchive export job

1

2

3

No connection

Running

Success! 🎉

State 

type pollingStatus = 
  | NotStarted
  | Loading(failedRequestsCount)
  | Success(apiResponse)
  | NetworkError(failedRequestsCount)
  | FatalError;
type action = 
  | PollRequest 
  | PollSuccess(jobStatus)
  | PollError;

State machine

let reducer = (state, action) => 
  switch (action) {
    | PollRequest => 
      switch (state) => {
        | NetworkError(failures) 
        | Loading(failures) => Loading(failures)
        | other => Loading(0)
      }
    | PollError => 
      switch (state) {
       | Loading(failures) when failures > 3 => FatalError
       | Loading(failures) => NetworkError(failures + 1)
       | other => other
     };
    | PollSuccess(_) => ...
  }

Typescript 🤔

type state = {
  isStarted: boolean
  isLoading: boolean
  failedRequests: number
  isError: boolean
  isFatalError: boolean
}

if (isError && !isFatalError && failedRequests < 3) {
  ...
  ...
}

  😱😭

Typescript 🤔

type state = 
  | { kind: "notStarted" }
  | { kind: "loading", failedRequests: number }  
  | { kind: "networkError", failedRequests: number }  
  | { kind: "fatalError" }
  | { kind: "success", apiResponse: jobStatus }
type pollingStatus = 
  | NotStarted
  | Loading(failedRequestsCount)
  | Success(apiResponse)
  | NetworkError(failedRequestsCount)
  | FatalError;

Making changes

Add "reconnect" button

type action = 
  | PollRequest 
  | PollSuccess
  | PollError
  | PollReset;

Restart polling timer on state change in React

switch (pollingState) {
 | FatalError => 
    <Text value="Internet not responding" />
    <Button text="Reconnect" onClick=startTimer />
 | Loading => <Spinner />
 | NetworkError(_) => <Text value="Reconnecting..." />
}

* 3 hours before code freeze 😨

Build times

Clean build < 3 mins

Incremental build ~ 1 s

Antura

819 .ts files, 45,800 LOC

Clean build ~ 20 s

Incremental build ~ 5-6 s

  • Variants, powerful pattern matching
  • Functional language, but pragmatic
    (has mutation, side effects)
  • Expressive, proven type system with strong type inference
  • Compilation speed
  • Interop with other libs - use anything on npm!
  • Possibility to compile to native code
  • Driven by Facebook

Summary

  • Hasn't reached a critical mass yet
  • Still "raw"
  • Tooling not on par with JS/TS

  • Sometimes tricky to find up-to-date libraries/bindings

Disadvantages

Adoption Strategies

No need to go all in from the start

  • Start with individual components
  • Start with logic/functions
  • Start with API decoders

Frontend Toolkit

React + TypeScript

Elm

WebForms

React + JavaScript

Vanilla JS/TS

ReasonReact

Frontend Toolkit

React + TypeScript

Official stack

Frontend Toolkit

React + TypeScript

React + ReasonML

Free choice when starting a new project/component

Official stack

Evaluation

Frontend Toolkit

WebForms

React + JavaScript

Vanilla JS/TS

Legacy

Razor/Blazor

(99% of AP)

(Gantt 2.0)

(Header, Kanban)

Frontend Toolkit

Elm

  • Existing code is (almost) bug-free
  • Existing code is easy to maintain
  • No reason to rewrite in React
  • Prerequisites for starting a new project:
    • A good reason
      (e.g. rendering performance)
    • Interop with existing code
      ​(e.g. via react-elm-components)

Companies hiring React JavaScript TypeScript developers

Companies hiring ReasonReact, Elm, etc. developers

React JS/TS developers

ReasonReact developers

Companies hiring React JavaScript TypeScript developers

Companies hiring ReasonReact, Elm, etc. developers

module Panel = {
  [@bs.module "./Panel"] [@react.component]
  external make:
    (
      ~scrollable: bool=?,
      ~className: string=?,
      ~style: ReactDOMRe.Style.t=?,
      ~children: React.element
    ) =>
    React.element =
    "Panel";
};

module Content = {
  [@bs.module "./Content"] [@react.component]
  external make:
    (
      ~scrollable: bool=?,
      ~disableScroll: bool=?,
      ~style: ReactDOMRe.Style.t=?,
      ~className: string=?,
      ~children: React.element
    ) =>
    React.element =
    "Content";
};

ReasonReact (condensed)

By Kyrylo Yakymenko

ReasonReact (condensed)

  • 132