$30 off During Our Annual Pro Sale. View Details »

Mixing ReasonML into your React apps

Mixing ReasonML into your React apps

ReasonML is a super interesting language and toolset but not all of us have the luxury of a brand new greenfield project to use it in. In this talk I'll explore some of the methods Bucklescript provides to mix ReasonML together with the Javascript in your projects. I'll also explore some ideas on the best areas in your project to get started converting JS to Reason, and the benefits you'll gain from doing so.

Robbie McCorkell

September 21, 2017
Tweet

Other Decks in Programming

Transcript

  1. MIXING REASONML INTO YOUR REACT APPS ROBBIE MCCORKELL @robbiemccorkell

  2. None
  3. FEATURES ▸ Functional let increment x => x + 1;

    let double x => x + x; let eleven = increment (double 5); ▸ Strong inferred type system let myInt = 5; let myTypedInt : int = 5; /* Same types */ ▸ Algebraic data types type myResponseVariant = | Yes | No | PrettyMuch; let areYouCrushingIt = Yes; ▸ Pattern matching let message = switch areYouCrushingIt { | No => "No worries. Keep going!" | Yes => "Great!" | PrettyMuch => "Nice!" }; /* message is "Great!" */
  4. None
  5. hacker-news hacker-news/ |— bundledOutputs/ |— lib/ |— node_modules/ |— src/

    | |— app/ | | |— component.js | | |— index.js | |— actions.js | |— api.js | |— header.re | |— index.html | |— index.js | |— reducer.js |— .babelrc |— bsconfig.json |— package.json |— README.md |— webpack.config.js
  6. ➜ hacker-news git:(master) yarn add --dev bs-platform …… ➜ hacker-news

    git:(master) yarn add reason-react
  7. hacker-news hacker-news/ |— bundledOutputs/ |— lib/ |— node_modules/ |— src/

    | |— app/ | | |— component.js | | |— index.js | |— actions.js | |— api.js | |— header.re | |— index.html | |— index.js | |— reducer.js |— .babelrc |— bsconfig.json |— package.json |— README.md |— webpack.config.js
  8. hacker-news hacker-news/ |— bundledOutputs/ |— lib/ |— node_modules/ |— src/

    | |— app/ | | |— component.js | | |— index.js | |— actions.js | |— api.js | |— header.re | |— index.html | |— index.js | |— reducer.js |— .babelrc |— bsconfig.json |— package.json |— README.md |— webpack.config.js
  9. bsconfig.json { "name": "hacker-news", "reason": { "react-jsx": 2 }, "package-specs":

    ["commonjs", "es6"], "bsc-flags": [ "-bs-super-errors" ], "bs-dependencies": [ "reason-react", "bs-jest", "bs-enzyme" ], "sources": "src" } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 hacker-news/ |— bundledOutputs/ |— lib/ |— node_modules/ |— src/ | |— app/ | | |— component.js | | |— index.js | |— actions.js | |— api.js | |— header.re | |— index.html | |— index.js | |— reducer.js |— .babelrc |— bsconfig.json |— package.json |— README.md |— webpack.config.js
  10. package.json { "name": “hacker-news”, "private": true, "version": "0.1.0", "description": "",

    "main": “./src/index.js”, "scripts": { "test": "jest", "lint": "eslint --cache .", "start": "webpack -w", "watch": "bsb -make-world -w", "build": "bsb -make-world && yarn webpack", "clean": "bsb -clean-world", }, …… 1 2 3 4 5 6 7 8 9 10 11 12 13 14 … hacker-news/ |— bundledOutputs/ |— lib/ |— node_modules/ |— src/ | |— app/ | | |— component.js | | |— index.js | |— actions.js | |— api.js | |— header.re | |— index.html | |— index.js | |— reducer.js |— .babelrc |— bsconfig.json |— package.json |— README.md |— webpack.config.js
  11. index.js import ReactDOM from 'react-dom'; import React from 'react'; import

    { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import { Provider } from 'react-redux'; import rootReducer from ‘./reducer.js'; import App from './app'; const store = createStore(rootReducer, applyMiddleware(thunk)); ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('index'), ); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 hacker-news/ |— bundledOutputs/ |— lib/ |— node_modules/ |— src/ | |— app/ | | |— component.js | | |— index.js | |— actions.js | |— api.js | |— header.re | |— index.html | |— index.js | |— reducer.js |— .babelrc |— bsconfig.json |— package.json |— README.md |— webpack.config.js
  12. actions.js import { getTopStories as getApiTopStories } from './api'; export

    const RECEIVE_TOP_STORIES = 'RECEIVE_TOP_STORIES'; export const REQUEST_TOP_STORIES = 'REQUEST_TOP_STORIES'; export const requestTopStories = () => ({ type: REQUEST_TOP_STORIES, payload: {}, }); export const receiveTopStories = stories => ({ type: RECEIVE_TOP_STORIES, payload: { stories, }, }); export const getTopStories = () => async (dispatch) => { await dispatch(requestTopStories()); const topStories = await getApiTopStories(); return dispatch(receiveTopStories(topStories)); }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 hacker-news/ |— bundledOutputs/ |— lib/ |— node_modules/ |— src/ | |— app/ | | |— component.js | | |— index.js | |— actions.js | |— api.js | |— header.re | |— index.html | |— index.js | |— reducer.js |— .babelrc |— bsconfig.json |— package.json |— README.md |— webpack.config.js
  13. reducer.js import { RECEIVE_TOP_STORIES, REQUEST_TOP_STORIES } from './actions'; const initialState

    = { stories: [], loaded: false, }; export default (state, action) => { if (typeof state === 'undefined') { return initialState; } switch (action.type) { case REQUEST_TOP_STORIES: return { ...state, loaded: false, }; case RECEIVE_TOP_STORIES: return { ...state, stories: action.payload.stories, loaded: true, }; default: return state; } }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 hacker-news/ |— bundledOutputs/ |— lib/ |— node_modules/ |— src/ | |— app/ | | |— component.js | | |— index.js | |— actions.js | |— api.js | |— header.re | |— index.html | |— index.js | |— reducer.js |— .babelrc |— bsconfig.json |— package.json |— README.md |— webpack.config.js
  14. reducer.js import { RECEIVE_TOP_STORIES, REQUEST_TOP_STORIES } from './actions'; const initialState

    = { stories: [], loaded: false, }; export default (state, action) => { if (typeof state === 'undefined') { return initialState; } switch (action.type) { case REQUEST_TOP_STORIES: return { ...state, loaded: false, }; case RECEIVE_TOP_STORIES: return { ...state, stories: action.payload.stories, loaded: true, }; default: return state; } }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 hacker-news/ |— bundledOutputs/ |— lib/ |— node_modules/ |— src/ | |— app/ | | |— component.js | | |— index.js | |— actions.js | |— api.js | |— header.re | |— index.html | |— index.js | |— reducer.js |— .babelrc |— bsconfig.json |— package.json |— README.md |— webpack.config.js
  15. reducer.re let initialState = {stories: [], loaded: false}; 1 2

    3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 reducer.js
  16. reducer.re type story = { id: int, title: string, url:

    string }; type state = {stories : list story, loaded: bool}; let initialState: state = {stories: [], loaded: false}; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 // Generated by BUCKLESCRIPT VERSION 1.9.1, PLEASE EDIT WITH CARE 'use strict'; var initialState = /* record */[ /* stories : [] */0, /* loaded : false */0 ]; exports.initialState = initialState; /* No side effect */ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 reducer.js
  17. reducer.re type story = { id: int, title: string, url:

    string }; type state = {stories : list story, loaded: bool}; let initialState: state = {stories: [], loaded: false}; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 // Generated by BUCKLESCRIPT VERSION 1.9.1, PLEASE EDIT WITH CARE 'use strict'; var initialState = /* record */[ /* stories : [] */0, /* loaded : false */0 ]; exports.initialState = initialState; /* No side effect */ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 reducer.js
  18. reducer.re type story = { id: int, title: string, url:

    string }; type state = Js.t {. stories : array story, loaded : Js.boolean}; let initialState: state = {"stories": [||], "loaded": Js.false_}; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 // Generated by BUCKLESCRIPT VERSION 1.9.1, PLEASE EDIT WITH CARE 'use strict'; var initialState = { stories: /* array */[], loaded: false }; exports.initialState = initialState; /* initialState Not a pure module */ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 reducer.js
  19. reducer.js import { RECEIVE_TOP_STORIES, REQUEST_TOP_STORIES } from './actions'; const initialState

    = { stories: [], loaded: false, }; export default (state, action) => { if (typeof state === 'undefined') { return initialState; } switch (action.type) { case REQUEST_TOP_STORIES: return { ...state, loaded: false, }; case RECEIVE_TOP_STORIES: return { ...state, stories: action.payload.stories, loaded: true, }; default: return state; } }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 hacker-news/ |— bundledOutputs/ |— lib/ |— node_modules/ |— src/ | |— app/ | | |— component.js | | |— index.js | |— actions.js | |— api.js | |— header.re | |— index.html | |— index.js | |— reducer.js |— .babelrc |— bsconfig.json |— package.json |— README.md |— webpack.config.js
  20. reducer.re type story = { id: int, title: string, url:

    string }; type state = Js.t {. stories : array story, loaded : Js.boolean}; let initialState: state = {"stories": [||], "loaded": Js.false_}; let reducer state action => { }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 // Generated by BUCKLESCRIPT VERSION 1.9.1, PLEASE EDIT WITH CARE 'use strict'; var initialState = { stories: /* array */[], loaded: false }; function reducer(state, action) { } exports.initialState = initialState; exports.reducer = reducer; /* initialState Not a pure module */ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 reducer.js
  21. reducer.re type story = { id: int, title: string, url:

    string }; type state = Js.t {. stories : array story, loaded : Js.boolean}; let initialState: state = {"stories": [||], "loaded": Js.false_}; let reducer state action => { switch action##_type { } }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 // Generated by BUCKLESCRIPT VERSION 1.9.1, PLEASE EDIT WITH CARE 'use strict'; var initialState = { stories: /* array */[], loaded: false }; function reducer(state, action) { var match = action.type; switch (match) { } } exports.initialState = initialState; exports.reducer = reducer; /* initialState Not a pure module */ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 reducer.js
  22. reducer.re type story = { id: int, title: string, url:

    string }; type state = Js.t {. stories : array story, loaded : Js.boolean}; let initialState: state = {"stories": [||], "loaded": Js.false_}; let reducer state action => { switch action##_type { | "REQUEST_TOP_STORIES" => {"stories": [||], "loaded": Js.false_} | "RECEIVE_TOP_STORIES" => {"stories": action##payload##stories, "loaded": Js.true_} } }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 // Generated by BUCKLESCRIPT VERSION 1.9.1, PLEASE EDIT WITH CARE 'use strict'; var initialState = { stories: /* array */[], loaded: false }; function reducer(state, action) { var match = action.type; switch (match) { case "RECEIVE_TOP_STORIES" : return { stories: action.payload.stories, loaded: true }; case "REQUEST_TOP_STORIES" : return { stories: /* array */[], loaded: false }; } } exports.initialState = initialState; exports.reducer = reducer; /* initialState Not a pure module */ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 reducer.js
  23. >>>> Start compiling Rebuilding since [] BSB check build spec

    : OK ninja.exe -C lib/bs ninja: Entering directory `lib/bs' [2/2] Building src/reducer_2.mlast.d [1/1] Building src/reducer_2.cmj Warning number 8 /Users/robbiemccorkell/Desktop/hacker-news/src/reducer_2.re 9 ! let initialState: state = {"stories": [||], "loaded": Js.false_}; 10 ! 11 ! let reducer state action => { 12 ! switch action##_type { 13 ! | "REQUEST_TOP_STORIES" => {"stories": [||], "loaded": Js.false_} 14 ! | "RECEIVE_TOP_STORIES" => {"stories": action##payload##stories, " loaded": Js.true_} 15 ! } 16 ! }; You forgot to handle a possible value here, for example: ""
  24. reducer.re type story = { id: int, title: string, url:

    string }; type state = Js.t {. stories : array story, loaded : Js.boolean}; let initialState: state = {"stories": [||], "loaded": Js.false_}; let reducer state action => { switch action##_type { | "REQUEST_TOP_STORIES" => {"stories": [||], "loaded": Js.false_} | "RECEIVE_TOP_STORIES" => {"stories": action##payload##stories, "loaded": Js.true_} | _ => state } }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 // Generated by BUCKLESCRIPT VERSION 1.9.1, PLEASE EDIT WITH CARE 'use strict'; var initialState = { stories: /* array */[], loaded: false }; function reducer(state, action) { var match = action.type; switch (match) { case "RECEIVE_TOP_STORIES" : return { stories: action.payload.stories, loaded: true }; case "REQUEST_TOP_STORIES" : return { stories: /* array */[], loaded: false }; default: return state; } } exports.initialState = initialState; exports.reducer = reducer; /* initialState Not a pure module */ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 reducer.js
  25. reducer.js import { RECEIVE_TOP_STORIES, REQUEST_TOP_STORIES } from './actions'; const initialState

    = { stories: [], loaded: false, }; export default (state, action) => { if (typeof state === 'undefined') { return initialState; } switch (action.type) { case REQUEST_TOP_STORIES: return { ...state, loaded: false, }; case RECEIVE_TOP_STORIES: return { ...state, stories: action.payload.stories, loaded: true, }; default: return state; } }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 hacker-news/ |— bundledOutputs/ |— lib/ |— node_modules/ |— src/ | |— app/ | | |— component.js | | |— index.js | |— actions.js | |— api.js | |— header.re | |— index.html | |— index.js | |— reducer.js |— .babelrc |— bsconfig.json |— package.json |— README.md |— webpack.config.js
  26. VARIANTS ▸ Variant types type myResponseVariant = | Yes |

    No | PrettyMuch; let areYouCrushingIt = Yes; ▸ Pattern matching let message = switch areYouCrushingIt { | No => "No worries. Keep going!” | Yes => "Great!" | PrettyMuch => "Nice!" }; /* message is "Great!" */ ▸ Data wrapping type account = | None | Instagram string | Facebook string int; let myAccount = Facebook "Josh" 26; let friendAccount = Instagram "Jenny"; ▸ Pattern matching let greeting = switch (myAccount) { | None => "Hi!" | Facebook name age => "Hi " ^ name ^ ", you're a “ ^ (string_of_int age) ^ "- year-old." | Instagram name => "Hello " ^ name ^ "!" }
  27. reducer.re type story = { id: int, title: string, url:

    string }; type state = Js.t {. stories : array story, loaded : Js.boolean}; let initialState: state = {"stories": [||], "loaded": Js.false_}; let reducer state action => { let optionState = Js.Nullable.to_opt state; switch optionState { | None => initialState | Some state => { switch action##_type { | "REQUEST_TOP_STORIES" => {"stories": [||], "loaded": Js.false_} | "RECEIVE_TOP_STORIES" => {"stories": action##payload##stories, "loaded": Js.true_} | _ => state } } }; }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 … … function reducer(state, action) { if (state == null) { return initialState; } else { var match = action.type; switch (match) { case "RECEIVE_TOP_STORIES" : return { stories: action.payload.stories, loaded: true }; case "REQUEST_TOP_STORIES" : return { stories: /* array */[], loaded: false }; default: return state; } } } exports.initialState = initialState; exports.reducer = reducer; /* initialState Not a pure module */ … … 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 reducer.js
  28. [2/2] Building src/reducer_2.mlast.d [1/1] Building src/reducer_2.cmj Warning number 8 /Users/robbiemccorkell/Desktop/hacker-news/src/reducer_2.re

    11 ! let reducer state action => { 12 ! let optionState = Js.Nullable.to_opt state; 13 ! switch optionState { 14 ! | Some state => { 15 ! switch action##_type { 16 ! | "REQUEST_TOP_STORIES" => {"stories": [||], "loaded": Js.false_ } 17 ! | "RECEIVE_TOP_STORIES" => {"stories": action##payload##stories, "loaded": Js.true_} 18 ! | _ => state 19 ! } 20 ! } 21 ! }; 22 ! }; You forgot to handle a possible value here, for example: None
  29. reducer.re type story = { id: int, title: string, url:

    string }; type state = Js.t {. stories : array story, loaded : Js.boolean}; let initialState: state = {"stories": [||], "loaded": Js.false_}; let reducer state action => { let optionState = Js.Nullable.to_opt state; switch optionState { | None => initialState | Some state => { switch action##_type { | "REQUEST_TOP_STORIES" => {"stories": [||], "loaded": Js.false_} | "RECEIVE_TOP_STORIES" => {"stories": action##payload##stories, "loaded": Js.true_} | _ => state } } }; }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 … … function reducer(state, action) { if (state == null) { return initialState; } else { var match = action.type; switch (match) { case "RECEIVE_TOP_STORIES" : return { stories: action.payload.stories, loaded: true }; case "REQUEST_TOP_STORIES" : return { stories: /* array */[], loaded: false }; default: return state; } } } exports.initialState = initialState; exports.reducer = reducer; /* initialState Not a pure module */ … … 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 reducer.js
  30. actions_re.re type actionType = | ReceiveTopStories | RequestTopStories | Other

    string; let decodeActionType = fun | "RECEIVE_TOP_STORIES" => ReceiveTopStories | "REQUEST_TOP_STORIES" => RequestTopStories | actionType => Other actionType; let encodeActionType = fun | ReceiveTopStories => "RECEIVE_TOP_STORIES" | RequestTopStories => "REQUEST_TOP_STORIES" | Other actionType => actionType; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 src/ |— app/ | |— component.js | |— index.js |— actions.js |— actions_re.js |— api.js |— header.re |— index.html |— index.js |— reducer.js
  31. reducer.re open Actions_re; type story = { id: int, title:

    string, url: string }; type state = Js.t {. stories : array story, loaded : Js.boolean}; let initialState: state = {"stories": [||], "loaded": Js.false_}; let reducer state action => { let optionState = Js.Nullable.to_opt state; let actionType = decodeActionType action##_type; switch optionState { | None => initialState | Some state => switch actionType { | RequestTopStories => {"stories": [||], "loaded": Js.false_} | ReceiveTopStories => {"stories": action##payload##stories, "loaded": Js.true_} | Other _ => state } } }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 // Generated by BUCKLESCRIPT VERSION 1.9.2, PLEASE EDIT WITH CARE 'use strict'; var Actions_re = require("./actions_re.js"); var initialState = { stories: /* array */[], loaded: false }; function reducer(state, action) { var actionType = Actions_re.decodeActionType(action.type); if (state == null) { return initialState; } else if (typeof actionType === "number") { if (actionType !== 0) { return { stories: /* array */[], loaded: false }; } else { return { stories: action.payload.stories, loaded: true }; } } else { return state; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 reducer.js
  32. Warning number 8 /Users/robbiemccorkell/Desktop/hacker-news/src/reducer.re 17 ! | None => initialState

    18 ! | Some state => 19 ! switch actionType { 20 ! | ReceiveTopStories => {"stories": action##payload##stories, "load ed": Js.true_} 21 ! | Other _ => state 22 ! } 23 ! } 24 ! }; You forgot to handle a possible value here, for example: RequestTopStories >>>> Finish compiling >>>> Start compiling Rebuilding since [ [ 'change', 'reducer.re' ] ] BSB check build spec : OK ninja.exe -C lib/bs ninja: Entering directory `lib/bs' ninja: no work to do. >>>> Finish compiling
  33. reducer.re open Actions_re; type story = { id: int, title:

    string, url: string }; type state = Js.t {. stories : array story, loaded : Js.boolean}; let initialState: state = {"stories": [||], "loaded": Js.false_}; let reducer state action => { let optionState = Js.Nullable.to_opt state; let actionType = decodeActionType action##_type; switch (optionState, actionType) { | (Some _, ReceiveTopStories) => {"stories": action##payload##stories, "loaded": Js.true_} | (Some _, RequestTopStories) => {"stories": [||], "loaded": Js.false_} | (Some state, Other _) => state | (None, _) => initialState } }; let default = reducer; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 src/ |— app/ | |— component.js | |— index.js |— actions.js |— actions_re.re |— api.js |— header.re |— index.html |— index.js |— reducer.re
  34. index.js import ReactDOM from 'react-dom'; import React from 'react'; import

    { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import { Provider } from 'react-redux'; import rootReducer from '../lib/js/src/reducer.js'; import App from './app'; const store = createStore(rootReducer, applyMiddleware(thunk)); ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('index'), ); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 hacker-news/ |— bundledOutputs/ |— lib/ | |— js/ | | |— src/ | | | |— header.js | | | |— reducer.js |— node_modules/ |— src/ | |— app/ | | |— component.js | | |— index.js | |— actions.js | |— api.js | |— header.re | |— index.html | |— index.js | |— reducer.re |— .babelrc |— bsconfig.json |— package.json |— README.md |— webpack.config.js
  35. webpack.config.js const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const

    UglifyJSPlugin = require('uglifyjs-webpack-plugin'); const webpack = require('webpack'); module.exports = { entry: ['babel-polyfill', './src/index.js'], output: { path: path.join(__dirname, 'bundledOutputs'), filename: '[name].js', }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' }, { test: /\.(re|ml)$/, use: { loader: 'bs-loader', options: { module: 'es6', }, }, }, ], }, resolve: { extensions: ['.re', '.ml', '.js'], }, ... 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 … hacker-news/ |— bundledOutputs/ |— lib/ |— node_modules/ |— src/ | |— app/ | | |— component.js | | |— index.js | |— actions.js | |— api.js | |— header.re | |— index.html | |— index.js | |— reducer.js |— .babelrc |— bsconfig.json |— package.json |— README.md |— webpack.config.js
  36. index.js import ReactDOM from 'react-dom'; import React from 'react'; import

    { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import { Provider } from 'react-redux'; import rootReducer from ‘./reducer.re’; import App from './app'; const store = createStore(rootReducer, applyMiddleware(thunk)); ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('index'), ); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 hacker-news/ |— bundledOutputs/ |— lib/ | |— js/ | | |— src/ | | | |— header.js | | | |— reducer.js |— node_modules/ |— src/ | |— app/ | | |— component.js | | |— index.js | |— actions.js | |— api.js | |— header.re | |— index.html | |— index.js | |— reducer.re |— .babelrc |— bsconfig.json |— package.json |— README.md |— webpack.config.js
  37. WHAT HAVE WE GAINED? ▸ It’s hard to mess up

    our state shape This is: state (defined as Js.t {. loaded : Js.boolean, stories : array story }) But somewhere wanted: Js.t {. loaded : Js.boolean, stories : list 'a } Types for method stories are incompatible ▸ It’s hard to forget to handle actions You forgot to handle a possible value here, for example: RequestTopStories ▸ It’s hard to forget to handle edge cases You forgot to handle a possible value here, for example: None
  38. NOT COVERED ▸ Reason React API! let component = ReasonReact.statelessComponent

    "Header"; let make children => { ...component, render: fun _self => <div> (ReasonReact.arrayToElement children) </div> }; ▸ Importing JS into Reason external getElementsByClassName : string => array Dom.element = "document.getElementsByClassName" [@@bs.val];
  39. github.com/robbiemccorkell/reason-interop @robbiemccorkell #reasonml