with Reason
Incremental Type Safety
in React Apollo
Evans Hauser
Core Developer, Apollo
Apollo Project 🚀
import React from 'react';
import { render } from 'react-dom';
import ApolloClient from 'apollo-boost';
import { ApolloProvider } from 'react-apollo';
import App from ‘./App';
const client = new ApolloClient({
uri: ‘https://localhost:3010/graphql'
});
const ApolloApp = () => (
<ApolloProvider client={client}>
<App />
</ApolloProvider>
);
render(ApolloApp, document.getElementById('root'));)
index.js
import FreeJobPost from “./FreeJobPost.js”

import PromotedPost from “./PromotedPost.js”
…
render() {
...
<FreeJobPost info={info1} />
<FreeJobPost info={info2} />
<FreeJobPost info={info3} />
<PromotedPost info={info4} />
...
}
App.js Adding to the 🚀
What about Reason 🤔
npm install bs-platform
npm install reason-react
{
"name": “package-name",
"sources": {
"dir": "src",
"subdirs": [“components"],
},
"reason": {
"react-jsx": 2
},
"refmt": 3
}
bsconfig.json
How to add Reason to your 🚀
Build: bsb -make-world
Watch: bsb -make-world -w
import PromotedPost from “./PromotedPost.bs.js”
render() {
...
<FreeJobPost info={info1} />
<FreeJobPost info={info2} />
<FreeJobPost info={info3} />
<PromotedPost info={info4} />
...
}
App.js Inside the 🚀
let component =
ReasonReact.statelessComponent(“PromotedPost”);
let make = (~info, _children) => {
...component,
render: (self) =>
<div>
<Post text=info.text timeLeft=info.timeLeft />
</div>
};
let default =
ReasonReact.wrapReasonForJs(
~component,
(props) => make(~info=settingsInfoFromJs(props##info), [||])
);
PromotedPost.re Adding Reason
PromotedPost.re Adding Reason
let component =
ReasonReact.statelessComponent(“PromotedPost”);
let make = (~info, _children) => {
...component,
render: (self) =>
<div>
<Post text=info.text timeLeft=info.timeLeft />
</div>
};
let default =
ReasonReact.wrapReasonForJs(
~component,
(props) => make(~info=settingsInfoFromJs(props##info), [||])
);
PromotedPost.re Adding Reason
let component =
ReasonReact.statelessComponent(“PromotedPost”);
let make = (~info, _children) => {
...component,
render: (self) =>
<div>
<Post text=info.text timeLeft=info.timeLeft />
</div>
};
let default =
ReasonReact.wrapReasonForJs(
~component,
(props) => make(~info=settingsInfoFromJs(props##info), [||])
);
let component =
ReasonReact.statelessComponent(“Post”);
let make = (~text, ~timeLeft, _children) => {
…component,
render: (self) =>
<input
_type=“checkbox”
value=text
checked=(text > 0)
/>
};
Post.re Reason Subcomponent
Reason FTW
Strong Typing 💪
let component =
ReasonReact.statelessComponent(“Post”);
let make = (~text, ~timeLeft, _children) => {
…component,
render: (self) =>
<input
_type=“checkbox”
value=text
checked=(timeLeft > 0)
/>
};
Post.re Reason Subcomponent
Pitfall
🎉 JS Interop 🎉
let component =
ReasonReact.statelessComponent(“Post”);
let make = (~text, ~timeLeft, _children) => {
…component,
render: (self) =>
<input
_type=“checkbox”
value=text
checked=(Js.Boolean.to_js_boolean(timeLeft > 0))
/>
};
Post.re Javascript Interop
PromotedPost.re Adding Reason
let component =
ReasonReact.statelessComponent(“PromotedPost”);
let make = (~info, _children) => {
...component,
render: (self) =>
<div>
<Post text=info.text timeLeft=info.timeLeft />
</div>
};
let default =
ReasonReact.wrapReasonForJs(
~component,
(props) => make(~info=settingsInfoFromJs(props##info), [||])
);
PromotedPost.re More Interop
let component =
ReasonReact.statelessComponent(“PromotedPost”);
let make = (~info, _children) => {
...component,
render: (self) =>
<div>
<p>Promoted Posting:</p>
<Post text=info.text timeLeft=info.timeLeft />
</div>
};
let default =
ReasonReact.wrapReasonForJs(
~component,
(props) => make(~info=settingsInfoFromJs(props##info), [||])
);
type postInfo = {.
“text”: string,
“timeLeft”: number
};
let postInfoFromJs = (jsInfo : postInfo) => {
text: jsInfo##text,
timeLeft: jsInfo##timeLeft
};
const examplePosting = {
“text”: “description of posting”,
“timeLeft”: 200
};
Defining TypesPromotedPost.re
App.js
What about GraphQL 🤔
Typed Query Language
Strong Reason support
Creating separation through GraphQL
npm install graphql_ppx
npm install reason_apollo
{
"bs-dependencies": [
"reason-react",
“reason-apollo"
],
"ppx-flags": [
"./node_modules/graphql_ppx/ppx -ast-out"
],
}
bsconfig.json
Adding GraphQL to your 🚀
Separation through GraphQL
module PromotedPostQuery = [%graphql {|
query getPromotedPosts {
info {
text
timeLeft
}
}
|}];
let promotedPosts = PromotedPostQuery.make(());
GqlPost.re
Separation through GraphQL
let component = ReasonReact.statelessComponent("PromotedPost");
[@bs.module "react-apollo"] external gql: 'a =>
[@bs] ReasonReact.reactClass => ReasonReact.reactClass = "graphql";
let wrap = (gql (promotedPosts##query));
let default =
[@bs] wrap (ReasonReact.wrapReasonForJs(
~component,
(props : {..
"data": {..
"data": TodosQuery.t,
"loading": Js.boolean,
}
}) => make(~result=props, [||])
)
));
GqlPost.re
Separation through GraphQL
let make = (~result, _children) => {
...component,
render: (self) =>
if(Js.to_bool (result##data##loading)){
<div> <String text="Loading"/> </div>
} else {
let text = result##data##info##text;
let timeLeft = result##data##info##timeLeft;
<div>
<Post text=text timeLeft=timeLeft />
</div>
}
};
GqlPost.re
A Possible API
module PromotedPostQuery = [%graphql {|
query getPromotedPosts {
info {
text
timeLeft
}
}
|}];
let make = (~result, _children) => {
...component,
render: (self) =>
if(Js.to_bool (result##loading)){
<div> <String text="Loading"/> </div>
} else {
let text = result##data##info##text;
let timeLeft = result##data##info##timeLeft;
<div>
<Post text=info.text timeLeft=info.timeLeft />
</div>
}
};
let default = ReasonApollo.graphql (PromotedPostsQuery) {"PromotedPost", make);
Let’s collaborate!
Evans Hauser
@evanshauser
evans@apollograpql.com
• Next Steps:
• Ensure that your types work
across the client server
boundary
• Manage client-side state with
apollo-link-state
Huge thank you to Ben Souci for his
help putting together this talk!

Incremental Type Safety in React Apollo

  • 1.
    with Reason Incremental TypeSafety in React Apollo Evans Hauser Core Developer, Apollo
  • 2.
    Apollo Project 🚀 importReact from 'react'; import { render } from 'react-dom'; import ApolloClient from 'apollo-boost'; import { ApolloProvider } from 'react-apollo'; import App from ‘./App'; const client = new ApolloClient({ uri: ‘https://localhost:3010/graphql' }); const ApolloApp = () => ( <ApolloProvider client={client}> <App /> </ApolloProvider> ); render(ApolloApp, document.getElementById('root'));) index.js
  • 3.
    import FreeJobPost from“./FreeJobPost.js”
 import PromotedPost from “./PromotedPost.js” … render() { ... <FreeJobPost info={info1} /> <FreeJobPost info={info2} /> <FreeJobPost info={info3} /> <PromotedPost info={info4} /> ... } App.js Adding to the 🚀
  • 4.
  • 5.
    npm install bs-platform npminstall reason-react { "name": “package-name", "sources": { "dir": "src", "subdirs": [“components"], }, "reason": { "react-jsx": 2 }, "refmt": 3 } bsconfig.json How to add Reason to your 🚀 Build: bsb -make-world Watch: bsb -make-world -w
  • 6.
    import PromotedPost from“./PromotedPost.bs.js” render() { ... <FreeJobPost info={info1} /> <FreeJobPost info={info2} /> <FreeJobPost info={info3} /> <PromotedPost info={info4} /> ... } App.js Inside the 🚀
  • 7.
    let component = ReasonReact.statelessComponent(“PromotedPost”); letmake = (~info, _children) => { ...component, render: (self) => <div> <Post text=info.text timeLeft=info.timeLeft /> </div> }; let default = ReasonReact.wrapReasonForJs( ~component, (props) => make(~info=settingsInfoFromJs(props##info), [||]) ); PromotedPost.re Adding Reason
  • 8.
    PromotedPost.re Adding Reason letcomponent = ReasonReact.statelessComponent(“PromotedPost”); let make = (~info, _children) => { ...component, render: (self) => <div> <Post text=info.text timeLeft=info.timeLeft /> </div> }; let default = ReasonReact.wrapReasonForJs( ~component, (props) => make(~info=settingsInfoFromJs(props##info), [||]) );
  • 9.
    PromotedPost.re Adding Reason letcomponent = ReasonReact.statelessComponent(“PromotedPost”); let make = (~info, _children) => { ...component, render: (self) => <div> <Post text=info.text timeLeft=info.timeLeft /> </div> }; let default = ReasonReact.wrapReasonForJs( ~component, (props) => make(~info=settingsInfoFromJs(props##info), [||]) );
  • 10.
    let component = ReasonReact.statelessComponent(“Post”); letmake = (~text, ~timeLeft, _children) => { …component, render: (self) => <input _type=“checkbox” value=text checked=(text > 0) /> }; Post.re Reason Subcomponent
  • 11.
  • 12.
    let component = ReasonReact.statelessComponent(“Post”); letmake = (~text, ~timeLeft, _children) => { …component, render: (self) => <input _type=“checkbox” value=text checked=(timeLeft > 0) /> }; Post.re Reason Subcomponent
  • 13.
  • 14.
    let component = ReasonReact.statelessComponent(“Post”); letmake = (~text, ~timeLeft, _children) => { …component, render: (self) => <input _type=“checkbox” value=text checked=(Js.Boolean.to_js_boolean(timeLeft > 0)) /> }; Post.re Javascript Interop
  • 15.
    PromotedPost.re Adding Reason letcomponent = ReasonReact.statelessComponent(“PromotedPost”); let make = (~info, _children) => { ...component, render: (self) => <div> <Post text=info.text timeLeft=info.timeLeft /> </div> }; let default = ReasonReact.wrapReasonForJs( ~component, (props) => make(~info=settingsInfoFromJs(props##info), [||]) );
  • 16.
    PromotedPost.re More Interop letcomponent = ReasonReact.statelessComponent(“PromotedPost”); let make = (~info, _children) => { ...component, render: (self) => <div> <p>Promoted Posting:</p> <Post text=info.text timeLeft=info.timeLeft /> </div> }; let default = ReasonReact.wrapReasonForJs( ~component, (props) => make(~info=settingsInfoFromJs(props##info), [||]) );
  • 17.
    type postInfo ={. “text”: string, “timeLeft”: number }; let postInfoFromJs = (jsInfo : postInfo) => { text: jsInfo##text, timeLeft: jsInfo##timeLeft }; const examplePosting = { “text”: “description of posting”, “timeLeft”: 200 }; Defining TypesPromotedPost.re App.js
  • 18.
    What about GraphQL🤔 Typed Query Language Strong Reason support
  • 19.
  • 20.
    npm install graphql_ppx npminstall reason_apollo { "bs-dependencies": [ "reason-react", “reason-apollo" ], "ppx-flags": [ "./node_modules/graphql_ppx/ppx -ast-out" ], } bsconfig.json Adding GraphQL to your 🚀
  • 21.
    Separation through GraphQL modulePromotedPostQuery = [%graphql {| query getPromotedPosts { info { text timeLeft } } |}]; let promotedPosts = PromotedPostQuery.make(()); GqlPost.re
  • 22.
    Separation through GraphQL letcomponent = ReasonReact.statelessComponent("PromotedPost"); [@bs.module "react-apollo"] external gql: 'a => [@bs] ReasonReact.reactClass => ReasonReact.reactClass = "graphql"; let wrap = (gql (promotedPosts##query)); let default = [@bs] wrap (ReasonReact.wrapReasonForJs( ~component, (props : {.. "data": {.. "data": TodosQuery.t, "loading": Js.boolean, } }) => make(~result=props, [||]) ) )); GqlPost.re
  • 23.
    Separation through GraphQL letmake = (~result, _children) => { ...component, render: (self) => if(Js.to_bool (result##data##loading)){ <div> <String text="Loading"/> </div> } else { let text = result##data##info##text; let timeLeft = result##data##info##timeLeft; <div> <Post text=text timeLeft=timeLeft /> </div> } }; GqlPost.re
  • 24.
    A Possible API modulePromotedPostQuery = [%graphql {| query getPromotedPosts { info { text timeLeft } } |}]; let make = (~result, _children) => { ...component, render: (self) => if(Js.to_bool (result##loading)){ <div> <String text="Loading"/> </div> } else { let text = result##data##info##text; let timeLeft = result##data##info##timeLeft; <div> <Post text=info.text timeLeft=info.timeLeft /> </div> } }; let default = ReasonApollo.graphql (PromotedPostsQuery) {"PromotedPost", make);
  • 25.
    Let’s collaborate! Evans Hauser @evanshauser evans@apollograpql.com •Next Steps: • Ensure that your types work across the client server boundary • Manage client-side state with apollo-link-state Huge thank you to Ben Souci for his help putting together this talk!