Slide 1

Slide 1 text

MIXING REASONML INTO YOUR REACT APPS ROBBIE MCCORKELL @robbiemccorkell

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

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!" */

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

➜ hacker-news git:(master) yarn add --dev bs-platform …… ➜ hacker-news git:(master) yarn add reason-react

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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( , 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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

>>>> 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: ""

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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 ^ "!" }

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

[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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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( , 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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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( , 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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

NOT COVERED ▸ Reason React API! let component = ReasonReact.statelessComponent "Header"; let make children => { ...component, render: fun _self =>
(ReasonReact.arrayToElement children)
}; ▸ Importing JS into Reason external getElementsByClassName : string => array Dom.element = "document.getElementsByClassName" [@@bs.val];

Slide 39

Slide 39 text

github.com/robbiemccorkell/reason-interop @robbiemccorkell #reasonml