Reason-apollo
react-apollo with ReasonML
Install and setup
Install
yarn add reason-apollo
# Add graphql_ppx
yarn add @baransu/graphql_ppx_re --dev
bsconfig
Add reason-apollo
to your bs-dependencies
and
@baransu/graphql_ppx_re/ppx
to your ppx-flags
bsconfig.json
"bs-dependencies": [
"reason-react",
"reason-apollo"
],
"ppx-flags": [
"@baransu/graphql_ppx_re/ppx"
]
Send introspection query
This will generate a graphql_schema.json
which will be used to safely type your GraphQL queries/mutations.
npx get-graphql-schema ENDPOINT_URL -j > graphql_schema.json
Why reason-apollo?
Watch its usage in this video:
Usage
Create the Apollo Client
Client.re
/* Create an InMemoryCache */let inMemoryCache = ApolloInMemoryCache.createInMemoryCache(); /* Create an HTTP Link */let httpLink = ApolloLinks.createHttpLink(~uri="http://localhost:3010/graphql", ()); let instance = ReasonApollo.createApolloClient(~link=httpLink, ~cache=inMemoryCache, ());
ApolloProvider
Index.re
/* Enhance your application with the `ReasonApollo.Provider` passing it your client instance */ReactDOMRe.renderToElementWithId( <ReasonApollo.Provider client=Client.instance> <App /> </ReasonApollo.Provider>, "index",);
Query
MyQuery.re
/* Create a GraphQL Query by using the graphql_ppx */module GetUserName = [%graphql {| query getUserName($id: ID!){ user(id: $ID) { id device { id brand { id name } } } }|}]; module GetUserNameQuery = ReasonApollo.CreateQuery(GetUserName); [@react.component]let make = () => { let userNameQuery = GetUserName.make(~id="42", ()); <GetUserNameQuery variables=userNameQuery##variables> ...{({result}) => switch (result) { | Loading => <div> {ReasonReact.string("Loading")} </div> | Error(error) => <div> {ReasonReact.string(error##message)} </div> | Data(response) => <div> {/* Handles a deeply nested optional response */ response##user ->Belt.Option.flatMap(user => user##device) ->Belt.Option.flatMap(device => device##brand) ->Belt.Option.mapWithDefault("", brand => brand##name)} </div> } } </GetUserNameQuery>;};
Mutation
MyMutation.re
module AddUser = [%graphql {| mutation addUser($name: String!) { addUser(name: $name) { id name } }|}]; module AddUserMutation = ReasonApollo.CreateMutation(AddUser); [[@react.component]let make = () => { <AddUserMutation> ...{(mutation /* Mutation to call */, _ /* Result of your mutation */) => { let addNewUserQuery = AddUser.make(~name="Bob", ()); <div> <button onClick={_mouseEvent => mutation( ~variables=addNewUserQuery##variables, ~refetchQueries=[|"getAllUsers"|], (), ) |> ignore }> {ReasonReact.string("Add User")} </button> </div>; }} </AddUserMutation>;};
Subscription
MySubscription.re
module UserAdded = [%graphql {|subscription userAdded { userAdded { id name }}|}]; module UserAddedSubscription = ReasonApollo.CreateSubscription(UserAdded); [@react.component]let make = () => { <UserAddedSubscription> ...{({result}) => { switch (result) { | Loading => <div> {ReasonReact.string("Loading")} </div> | Error(error) => <div> {ReasonReact.string(error##message)} </div> | Data(_response) => <audio autoPlay=true> <source src="notification.ogg" type_="audio/ogg" /> <source src="notification.mp3" type_="audio/mpeg" /> </audio> } }} </UserAddedSubscription>;};
ApolloConsumer
If you simply want to have access to the ApolloClient, you can use the ApolloConsumer
<ApolloConsumer> ...{apolloClient => {/* We have access to the client! */}}</ApolloConsumer>;
Tips and Tricks
access deeply nested optional objects
If for this query
query { user { device { brand { name } } }}
you end up with that kind of code:
let deviceName = switch (response##user) { | None => "" | Some(user) => switch (user##device) { | None => "" | Some(device) => switch (device##brand) { | None => "" | Some(brand) => brand##name } } };
- Use
Belt
open Belt.Option; let deviceName = response##user ->flatMap(user => user##device) ->flatMap(device => device##brand) ->mapWithDefault("", brand => brand##name);
- Use
@bsRecord
The @bsRecord
modifier is an extension of the graphql syntax for BuckleScipt/ReasonML. It allows you to convert a reason object to a reason record and reap the benefits of pattern matching, but you need to defined the record by yourself.
type brand = { name: string}; type device = { brand: option(brand)}; type user = { device: option(device)}; type response = user; query { user @bsRecord { device @bsRecord { brand @bsRecord { name } } }}
This time we can pattern match more precisely.
let deviceName = switch (response##user) { | Some({device: Some({brand: {name}})}) => name | _ => "" };
- Use
get_in_ppx
npm install get_in_ppx
and in bsconfig.json
"ppx-flags": ["get_in_ppx/ppx"]
you can write
let deviceName = response##user#??device#??brand#?name;
There's a blogpost from Jared Forsyth (author of this ppx) for more explanation.
Use an alias for irregular field names
You might find yourself consuming an API with field names like Field
. Currently, reason object field names are required to be camel case. Therefore if you have a request like this:
{ Object { id title }}
You will attempt to access the response object but it will throw an error:
response##Object; /* Does not work :( */
Instead, use an alias
to modify the response:
{ object: Object { id title }}
Then you can access the object like this:
response##object
Generic Error and Loading components
You can create a generic error and Loading component and compose them like this example:
module QueryView = { [@react.component] let make = ( ~result: ReasonApolloTypes.queryResponse('a), ~accessData: 'a => option('b), ~render: ('b, 'c) => React.element, ~onLoadMore: ('b, 'unit) => unit=(_, ()) => (), ) => { switch (result) { | Error(error) => <Error /> | Loading => ReasonReact.null | Data(response) => switch (accessData(response)) { | Some(data) => render(data, onLoadMore(data)) | _ => <Error error="" /> } }; };};
FAQ
I've added the schema file, but my build fails saying it couldn't be found?
In some cases, it seems like there are some differences between the provided send-introspection-query
and output from tools you might be using to download the schema (such as apollo-codegen
or graphql-cli
).
If your build is failing, please make sure to try with the provided script. In your project root, run:
npx get-graphql-schema ENDPOINT_URL -j > graphql_schema.json