We're planting a tree for every job application! Click here to learn more

When Reason meets GraphQL

Grégoire Vda

2 Mar 2018

3 min read

When Reason meets GraphQL
  • ReasonML

In this post I’ll show you how to integrate graphQL into a Reason todo list app made with ReasonReact library, thanks to Apollo. I’ll try to be concise and show you working code. The code I show comes from this repo. If you’re new to Reason, go checkout their docs and if you’re looking for more details on how to create a simple todo list, checkout this tutorial. Here we go…

Our root.re is our starting point to render the App component in the DOM element with has the “index” id. (which you can find in the index.html file)

ReactDOMRe.renderToElementWithId <App /> "index";

Our app.re is a stateless component. It enhances the application with ApolloProvider. We pass him a graphQL client and it will make it available for us in any component through the context.

let component = ReasonReact.statelessComponent "App";

let make _children => {
  ...component, /* Extend default */
  render: fun _self => {
    <ApolloProvider client=ApolloClient.instance> 
      <DataTodoContainer /> 
    </ApolloProvider>
  }
};

Here’s our apolloClient.re where the ApolloClient is created :

/* Js typed Object */
type networkInterface = Js.t {. uri : string}; 
/* Js typed Object, holding the above networkInterface */
type apolloClient = Js.t {. networkInterface : string};
/* Import ApolloClient from 'react-apollo'. [@@bs.new] is BuckleScript syntax to tell it needs a new instance. We name it apollo_client and type it as apolloClient (declared above) */
external apollo_client : apolloClient => string =
  "ApolloClient" [@@bs.new] [@@bs.module "react-apollo"];
/* Import createNetworkInterface from 'react-apollo' */
external create_network_interface : networkInterface => string =
  "createNetworkInterface" [@@bs.module "react-apollo"];
/* Create the networkInterface. Types are inferred */
let networkInterfaceInstance = {"uri": "http://localhost:3010/graphql"};
/* Create the apolloClientConfig. Types are again inferred */
let apolloClientConfig = {"networkInterface": create_network_interface networkInterfaceInstance};
/* Thanks to the [@@bs.new] it will create a new ApolloClient instance with the configuration passed as parameter. The instance is available in other files through ApolloClient.instance */
let instance = apollo_client apolloClientConfig;

In the dataTodoContainer.re, we create our queries and mutations and enhance the todoContainer.re with them.

/* we give the query (under the hood, it's a string) an opaque type. This way nobody can accidentally use it as a string  */
type query;

/*
annotate the function with [@bs] so that it's statically verified to be fully applied at each callsite. Better perf from fewer curryings. See more info on `[@bs]` in the BS manual
*/
type gql = (string => query) [@bs];
/* We import the default export of 'graphql-tag' */
external gql : gql = "graphql-tag" [@@bs.module];

We need to use the graphql HoC with and without configuration as 2nd parameter. To do so, we assign the graphql import from ‘react-apollo’ to a graphql function, typed to only receive a query. And we assign it to graphqlWithConfig, typed to be called with a query and a configuration parameter.

/* 
HoC signature, takes a ReactClass as param and returns a ReactClass */
type wrapper = (ReasonReact.reactClass => ReasonReact.reactClass) [@bs];
type graphqlConfig;
/* 
Define the configuration parameter as an Object, where only the name is required 
*/
external graphqlConfig :
  name::string =>
  alias::string? =>
  skip::Js.boolean? =>
  skip__func::'skipFunc? =>
  unit =>
  graphqlConfig =
  "" [@@bs.obj];
/* HoC that takes a query as parameter */
type graphql = (query => wrapper) [@bs];
/* HoC that takes a query and a configuration Object as parameter */
type graphqlWithConfig = (query => graphqlConfig => wrapper) [@bs];
/* 
We import graphql from 'react-apollo twice and assign it to a method with a specific signature'
*/
external graphql : graphql = "graphql" [@@bs.module "react-apollo"];
external graphqlWithConfig : graphqlWithConfig = "graphql" [@@bs.module "react-apollo"];

Here’s an example of a query

let todos_query =
  gql {|
  query getAllTodos {
    todos {
      id
      title
      active
    }
  }
|} [@bs];

And a mutation

let add_todo_mutation =
  gql
  {|
  mutation addTodo($title: String!, $active: Boolean!) {
    addTodo(
        title: $title,
        active: $active
    ) {
      id
      title
      active
    }
  }
|}
  [@bs];

We now start chaining our queries and mutations with our HoC’s:

/* 
Call the graphqlWithConfig method with the mutation and the configuration object, with a name 
*/
let addTodoWrapper = graphqlWithConfig add_todo_mutation (graphqlConfig name::"addTodoMutation" ()) [@bs];
/*
The wrapper is called on the TodoContainer to enhance it with new props. (TodoContainer is a simple component coming from another file)
*/
let wrappedAddTodoComponent: ReasonReact.reactClass =
  addTodoWrapper TodoContainer.jsComponent [@bs];

We do the same for the query

let queryWrapper = graphqlWithConfig todos_query (graphqlConfig name::"todosQuery"()) [@bs];

let wrappedQueryTodosComponent: ReasonReact.reactClass =
  queryWrapper wrappedAddTodoComponent [@bs];

We now export this, but the annoying part is that the graphql wrapper expects a ReactJS class, but remember that a ReasonReact class isn’t a ReactJS class, so you need to convert it to JS: Reason → JS

let make children =>
  ReasonReact.wrapJsForReason
    reactClass::wrappedDeleteTodoMutationComponent props::(Js.Obj.empty ()) children;

and then back to Reason: JS → Reason

let jsComponent = ReasonReact.wrapReasonForJs ::component (fun props => {
       make
       addTodoMutation::props# addTodoMutation
       todosQuery::props# todosQuery
       [||];
}

Our component is now fully enhanced with all queries and mutations we need. We can access them in the props of the enhanced component

let make ::todosQuery ::addTodoMutation _children => {
  ...component,
  render: fun _self => {
    <div>
     /* 
      todos are available like so: todosQuery# todos,
      and the mutation can be called with variabled like that
      let mutation = {
           "variables": {
              "title": text,
              "active": Js.true_
          }
      };
      addTodoMutation mutation
      */
</div>
  }
};

Conclusion

Reason integrates really well with GraphQL and the typings Reason offers us has a lot of benefits over any JS library. In the future we will be able to cut out the Reason → JS → JS → Reason and make it even easier.

Come join the Reason community on Discord and contribute to this great tech!


If you’re passionate about Front End development, check out the JavaScript Works job-board here!


@gregoirevda

Did you like this article?

Related jobs

See all

Title

The company

  • Remote

Title

The company

  • Remote

Title

The company

  • Remote

Title

The company

  • Remote

Related articles

JavaScript Functional Style Made Simple

JavaScript Functional Style Made Simple

Daniel Boros

12 Sep 2021

JavaScript Functional Style Made Simple

JavaScript Functional Style Made Simple

Daniel Boros

12 Sep 2021

WorksHub

CareersCompaniesSitemapFunctional WorksBlockchain WorksJavaScript WorksAI WorksGolang WorksJava WorksPython WorksRemote Works
hello@works-hub.com

Ground Floor, Verse Building, 18 Brunswick Place, London, N1 6DZ

108 E 16th Street, New York, NY 10003

Subscribe to our newsletter

Join over 111,000 others and get access to exclusive content, job opportunities and more!

© 2024 WorksHub

Privacy PolicyDeveloped by WorksHub