GithubHelp home page GithubHelp logo

reason-apollo's Introduction

Archival

This repo was archived by the Apollo Security team on 2023-05-26

Reason-apollo

npm version Join the community on Spectrum

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:

Watch reason-apollo usage here

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
      }
    }
  };
  1. Use Belt
open Belt.Option;

let deviceName =
  response##user
  ->flatMap(user => user##device)
  ->flatMap(device => device##brand)
  ->mapWithDefault("", brand => brand##name);
  1. 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
  | _ => ""
  };
  1. 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

reason-apollo's People

Contributors

arecvlohe avatar aried3r avatar arnarthor avatar baransu avatar dependabot[bot] avatar drejohnson avatar elnygren avatar emmenko avatar excitement-engineer avatar fakenickels avatar gregoirevda avatar happylinks avatar heygema avatar idkjs avatar jakubmarkiewicz avatar jeffutter avatar marcelcutts avatar margaretkrutikova avatar medson10 avatar moox avatar rk1 avatar superherointj avatar svsool avatar tmattio avatar ulrikstrid avatar vinhlh avatar wawhal avatar yawaramin avatar yukims19 avatar zhzhang avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

reason-apollo's Issues

Imperative API for mutation

Let's me explain the problem:

<Apollo.Client.Mutation>
  (
    (mutate, result) => {
      let mutationResponse =
        switch (result) {
        | NotCalled => <div> ("Not Called" |> text) </div>
        | Loading => <div> ("Loading" |> text) </div>
        | Loaded(response) => <div> ("Loaded" |> text) </div>
        | Failed(error) => <div> ("Error" |> text) </div>
        };
      <PatientAddFormContainer
        initialState=...
        onSubmit=(
          (values, ~setSubmitting, ~setError as _) => {
            let mutation = AddPatientMutation.make(~name=values.name, ());
            setSubmitting(true);
            mutate(mutation);
          }
        )
        schema=...>
        ...
      </PatientAddFormContainer>;
    }
  )
</Apollo.Client.Mutation>

The problem I'm facing is I have no way to setSubmiting(false) or setError(...)

I want to promote a new api for Mutation that looks like this:

<Apollo.Client.Mutation>
  (
    (_mutate, result, mutatePromise) => {

    }
  )
</Apollo.Client.Mutation>

Now I can do all the imperative things with mutatePromise

My proposed API could also solve this #33

Explanation/Decoding Optional Responses?

I'm wondering why the following code, which is basically the same as the examples/swapi example, is throwing the options error when it doesnt do it for the swapi code. Two things I can see is that in the examples/swapi the person type used implements node.

The question is why does reason-apollo handle these differently?

Also, how do I decode the response given the error.

repo with this example code.

This is the type in this example:

type Person {
  age: Int!
  id: ID!
  name: String!
}
// type from `examples/swapi`
type Person implements Node {
  age: Int!
  id: ID!
  name: String!
}

Running a query:

   module GetAllPersons = [%graphql
     {|
       query GetList {
         listPeople {
           items {
             age
             id
             name
           }
         }
       }
   |}
   ];

Response:

{
  "data": {
    "listPeople": {
      "items": [
        {
          "age": 23,
          "id": "123",
          "name": "BobTest"
        },
        {
          "age": 24,
          "id": "124",
          "name": "JoeTest"
        },
        {
          "age": 25,
          "id": "125",
          "name": "JimTest"
        }
      ]
    }
  }
}

In the component, i'm trying to us it like so:

let make = _children => {
  ...component,
  render: _self =>
    <GetAllPersonsQuery>
      ...(
           ({result}) =>
             <div>
               <h1> ("Persons: " |> ste) </h1>
               (
                 switch (result) {
                 | Error(e) =>
                   Js.log(e);
                   "Something Went Wrong" |> ste;
                 | Loading => "Loading" |> ste
                 | Data(response) =>
                   response##listPeople##items
                   |> Array.mapi((index, person) =>
                        <div key=(index |> string_of_int)>
                          (person##name |> ste)
                          <br />
                          <p> ("ID: " ++ person##id |> ste) </p>
                        </div>
                      )
                   |> ReasonReact.array
                 }
               )
             </div>
         )
    </GetAllPersonsQuery>,
};

This generates the following error:

  We've found a bug for you!
  /Users/prisc_000/code/REASON/APOLLO/reason-apollo-question/src/Persons.re 36:20-39

  34 โ”† | Loading => "Loading" |> ste
  35 โ”† | Data(response) =>
  36 โ”†   response##listPeople##items
  37 โ”†   |> Array.mapi((index, person) =>
  38 โ”†        <div key=(index |> string_of_int)>

  This has type:
    option({. "items": option(Js.Array.t(option({. "age": int, "id":
                                                  string, "name": string})))})
  But somewhere wanted:
    Js.t('a)

[4/6] Building ient-ReactTemplate.cmj
ninja: build stopped: subcommand failed.
>>>> Finish compiling(exit: 1)

I was going to post this to stackoverflow after making just a decode question.
Thanks for the clarification.

Mutation documentation

Hi,

It seems like the documentation for mutations in the readme is incomplete and/or not working. I tried to fix it but couldn't get it working with the different types of the "result" param.

Will try some more tomorrow but if someone can gives a hint, would be very much appreciated!

Michiel

Issue Labels

  • has-reproduction
  • feature
  • docs
  • blocking
  • good first issue

How to make side effects after mutation?

Currently we are passing component logic as children of Mutation, when it comes to make some side effects according to the mutation result, such as, when the mutation success, call a prop passed from parent component, which will modify state of the parent component. In the situation, I will receive a warning:

Warning: setState(...): Cannot update during an existing state transition

// Parent.re
let make = _children => {
  ...component,
  initialState: () => {isLogin: false},
  reducer: ((), state) => ReasonReact.Update({isLogin: ! state.isLogin}),
  render: self => <Login onSuccess=self.send />
};
// Login.re
let make = (~onSuccess, _children) => {
  ...component,
  initialState: () => {username: "", password: ""},
  reducer: (action, state) =>
    switch action {
    | LoginSuccess =>
      onSuccess();
      ReasonReact.NoUpdate;
    },
  render: self =>
    <Mutation>
      ...(
           (mutate, result) => {
             let {username, password} = self.state;
             let mutation = LoginMutation.make(~username, ~password, ());
             let result =
               switch result {
               | NotCalled => <Text value="not called" />
               | Loading => <Text value="loading" />
               | Failed(error) => <Text value=error />
               | Loaded(res) =>
                 let parse = mutation##parse;
                 let login = parse(res)##login;
                 switch login {
                 | None => ReasonReact.nullElement
                 | Some(_) =>
                   self.send(LoginSuccess);  // side effect here
                   ReasonReact.nullElement;
                 };
               };
             <View>
                 <TouchableHighlight onPress=(() => mutate(mutation))>
                   <Text value="Login" />
                 </TouchableHighlight>
                 result
             </View>;
           }
         )
    </Mutation>
}

I have tried to put the Mutation in Parent.re and render Login.re with a prop to trigger the mutation, this way avoids the warning. Is this a good way to solve the problem? Or maybe we can pass a callback to handle this, like ApolloClient.mutation(options) can pass a update.

No way to invoke client.resetStore

  • feature

Currently there seems to be no implementation to invoke client.resetStore(), which is important when handling authentication with Apollo. I had attempted to implement this feature for a pull request (cdebotton@53eb4b4), but I'm a novice with ReasonML and ran into issues. It seems as though methods cannot be invoked off of the generatedApolloClient.

Would it be possible to add this functionality, or if anyone could give me some guidance, I'd be very happy to add this feature!

Incorrect Type for type apolloError

type apolloError = {
  .
  "message": string,
  "graphQLErrors": Js.Nullable.t(array(string)),
  "networkError": Js.Nullable.t(string)
};

The above type for apolloError's graphQLErrors is incorrect. It should be:

Js.Nullable.t(array(graphQLErrors))

Unite the reducer flow with the way it currently works

Issue Labels

  • feature
  • has-reproduction
  • docs
  • blocking
  • good first issue

Hi all, i'm new to this, but i have a couple of observations -
The way mutations/queries currently works feels very weird, ReasonReact introduced the concept of a reducer and state and the cycle should be event -> reducer -> update(newState)

but, ReasonApollo works completely differently, its not using the state at all.
Nor does it actually uses the reducer. aside from the fact that i think that the resulting code is very hard to read and reason about since now we have 2 data-flows in each component and we need to think about both.
It also complicates the render function with extra "magic".

I'm not sure how to solve this, but i would love to discuss a solution that actually uses ReasonReact idioms.
For example, why not have
type state = {
mutation: Mutation(..., MutationResult)
}

or something like this. in general the fact that i've to write about 5 different "components" to make a simple graph mutation/query feels very very repetitive

Parse is not a function in Basic Example

Issue Labels

  • [x ] has-reproduction
  • feature
  • blocking
  • good first issue

if you run the basic example it compiles but throws the following error in the browser

  125 |   return docCache[cacheKey];
  126 | }
  127 | 
> 128 | var parsed = parse(doc);
  129 | if (!parsed || parsed.kind !== 'Document') {
  130 |   throw new Error('Not a valid GraphQL document.');
  131 | }

screenshot

Demo repo reproducing error: https://github.com/idkjs/apollo-test

Already provide Mutation parsed result

Currently with the mutation imperative API the mutate function returns Js.Promise.t(Json.t) and the user needs to parsed manually with convertJsInputToReason. I think this additional step is not necessary and it could return just Js.Promise.t(Mutation.t).

What do you think?

cc @georgelima

Add module type for generated modules

Issue Labels

  • has-reproduction
  • feature
  • docs
  • blocking
  • good first issue

Components for queries and mutations contain a lot of boilerplate code. From what I understand (I am new to reason), it is presently difficult to write code common code to all queries/mutation because we actually generate new modules.

Would it make sense to add module types for these generated modules to be able to write general code?

A concrete use case would be to have the same function to handle errors for all queries.

Fatal error: exception Failure("graphql_ppx/pxx not found when resolving ppx-flags") when running bsb -make-world

Can repro with a new directory, adding just the following two files, and running npm install
package.json dependencies

"dependencies": {
    "apollo-cache-inmemory": "^1.2.6",
    "apollo-client": "^2.3.7",
    "apollo-link": "^1.2.2",
    "apollo-link-context": "^1.0.8",
    "apollo-link-error": "^1.1.0",
    "apollo-link-http": "^1.5.4",
    "babel-plugin-inline-import": "^3.0.0",
    "draft-js": "^0.10.5",
    "draft-js-plugins-editor": "^2.0.8",
    "express": "^4.16.3",
    "express-graphql": "^0.6.12",
    "firebase-admin": "^5.13.1",
    "graphql": "^0.13.2",
    "graphql-tag": "^2.9.2",
    "katex": "^0.10.0-alpha",
    "nodemon": "^1.18.3",
    "pdfjs-dist": "^2.0.489",
    "react": "^16.4.1",
    "react-apollo": "^2.1.9",
    "react-dom": "^16.2.0",
    "react-katex": "^2.0.2",
    "react-redux": "^5.0.7",
    "react-rnd": "^7.4.0",
    "reason-apollo": "^0.12.0",
    "reason-react": "^0.4.2",
    "redux": "^4.0.0",
    "webpack-dev-middleware": "^3.1.3",
    "webpack-hot-middleware": "^2.22.2"
  },  
  "devDependencies": {
    "babel": "^6.23.0",
    "babel-cli": "^6.26.0",
    "babel-loader": "^7.1.4",
    "babel-plugin-transform-class-properties": "^6.24.1",
    "babel-plugin-transform-object-rest-spread": "^6.26.0",
    "babel-preset-env": "^1.7.0",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-flow": "^6.23.0",
    "babel-preset-react": "^6.24.1",
    "bs-platform": "^3.1.5",
    "css-loader": "^0.28.11",
    "file-loader": "^1.1.11",
    "flow-bin": "^0.76.0",
    "graphql_ppx": "^0.2.6",
    "style-loader": "^0.21.0",
    "webpack": "^4.0.1",
    "webpack-cli": "^2.0.10"
  }

bsconfig

{
  "name": "react-template",
  "reason": {
    "react-jsx": 2
  },
  "sources": {
    "dir" : "src",
    "subdirs" : true
  },
  "package-specs": [{
    "module": "es6",
    "in-source": true
  }],
  "suffix": ".bs.js",
  "namespace": true,
  "bs-dependencies": [
    "reason-react",
    "reason-apollo"
  ],
  "ppx-flags": [
    "graphql_ppx/pxx"
  ],
  "refmt": 3
}

Issue Labels

  • has-reproduction
  • feature
  • docs
  • blocking
  • good first issue

Record type for "apolloError##networkError" is not correct

Issue Labels

  • has-reproduction
  • blocking
  • bug

In the ReasonApolloTypes we started expanding apollo error.

However, the networkError field is typed as string. It should be an object with more specific fields instead.

/* before */
type networkError = {. "statusCode": int}; /* <-- already existing */

type apolloError = {
  /* omitting some fields for simplicity */
  "networkError": Js.Nullable.t(string) /* <-- this should not be a string */
};

/* after */
type errorObj = {. "message": string};

type networkErrorResult = {. "errors": Js.Nullable.t(array(errorObj))};

type networkError = {
  .
  "statusCode": int,
  "result": networkErrorResult
}; /* <-- already existing */

type apolloError = {
  /* omitting some fields for simplicity */
  "networkError": Js.Nullable.t(networkError)
};

Now this is where it might get trickier: the networkErrorResult I assume depends on what kind of response the server will send. In the example above, I assume the server returns a "graphql-like" error response (e.g. a 400 validation error in case the query is wrong).

I suppose that we could model this in a more generic way, using an abstract type:

type networkErrorResult;

type networkError = {
  .
  "statusCode": int,
  "result": networkErrorResult;
};

This way we don't make any assumptions about the shape of the result and let the consumer type it.

type errorObj = {. "message": string};

type myOwnResult = {. "errors": Js.Nullable.t(array(errorObj))};

external toMyOwnResult = 'a => myOwnResult = "%identity";

/* usage */
toMyOwnResult(networkError##result);

Is there any other way of doing this properly? Does it even make sense of doing it? How do you usually handle those kind of network errors? Do you just log them?

The module or file ReasonApollo.CreateClient can't be found.

I've tried to use this project twice at different times for different personal projects and I've always received the error below after following the readme:

19 โ”‚
20 โ”‚ module Client =
21 โ”‚ ReasonApollo.CreateClient(
22 โ”‚ {
23 โ”‚ let apolloClient =

The module or file ReasonApollo.CreateClient can't be found.

  • If it's a third-party dependency:
    • Did you list it in bsconfig.json?
    • Did you run bsb instead of bsb -make-world
      (latter builds third-parties)?
  • Did you include the file's directory in bsconfig.json?

I'm sure I follow the instructions perfectly. What can I do? I'm new to Reason.

bool type vs Js.boolean type conflict?

Issue Labels

I'm testing reason-apollo@beta and getting a type error when Im trying to pass the returned data to a component. Apparently, bs-platform 3.0 doesnt make a distinction between Js.boolean and bool types while ReasonApollo.response is returning a Js.boolean type, when i pass the parsed result to another component, that component assumes the type is bool which results in following type error. Fully explained in reason.chat.

This doesn't work.src/Feedpage.re

This has type:
    array(Js.t(({.. id: string, isPublished: bool, text: string,
                  title: string}
                as 'a))) =>
    array(ReasonReact.reactElement)
  But somewhere wanted:
    Js.Array.t({. "id": string, "isPublished": Js.boolean, "text": string,
                 "title": string}) =>
    'b

  The incompatible parts:
    array(Js.t('a))
    vs
    Js.Array.t({. "id": string, "isPublished": Js.boolean, "text": string,
                 "title": string})
      (defined as
      array({. "id": string, "isPublished": Js.boolean, "text": string,
              "title": string}))
    at <anonymous> isPublished are incompatible

While the does.

response##feed
                   |> Array.mapi((index, post) =>
                        <div key=(index |> string_of_int)>
                          (post##title |> ste)
                        </div>
                      )
                   |> ReasonReact.arrayToElement

I can't figure out why the type is changing which is preventing me from using the result of the boolean in a function. Ideas? Thank you.

ApolloDevTools Setup

Seems like ApolloDevTools isnt working when passed to client instance.

The error in devtools panel is:

Source map error: request failed with status 404
Resource URL: http://127.0.0.1:5500/examples/swapi/build/index.js
Source Map URL: utilities.js.map

This is the setup in this repos swapi example:

module Instance =
  ReasonApollo.CreateClient(
    {
      let apolloClient =
        ReasonApollo.createApolloClient(
          ~cache=inMemoryCache /* restore method can be piped e.g. inMemoryCache |> restore(window.__APOLLO__) */,
          ~link=httpLink,
          ~connectToDevTools=Js.true_,
          ()
        );
    }
  );

First note is that you have to start a server for devtool to recognize the app, you cant just open index.html. I opened with Live Server in VS Code.

Once you get it open, running queries doesn't return anything. Im sure I have this set up incorrectly.

https://gyazo.com/de2633bd91aca75267b1316a62a2f18b

Response shows up in ui and in network tab:

https://gyazo.com/e98461eed9016c5c5287354a5eafcd81

Thanks.

Using Reason-Apollo with Create-React-App

Issue Labels

  • has-reproduction
  • feature
  • docs
  • blocking
  • good first issue

I took the examples/swapi demo and tried to run it with create-react-app`.
reproduction
This is the error in browser. Project compiles successfully.
screenshot

Any idea what is going on here?

Error Accessing JS Property

Using:
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]

In an attempt to follow the tutorial, I came across this error:

  We've found a bug for you!
app.re 72:51-52

  70 โ”†   | Loading => <div>(str("Loading"))</div>
  71 โ”†   | Error(error) => (Js.log(error));<div></div>
  72 โ”†   | Data(response) => (Js.log(response##_User##username));<div></div>
  73 โ”†
  74 โ”† }

  This expression has type {. User: option({. "username": string})}
  It has no method _User

ninja: build stopped: subcommand failed.
>>>> Finish compiling(exit: 1)
module GetUser = [%graphql
  {|
    query getUser ($pk: ID!){
      User(id: $pk){
          username
      }
    }
|}
];

module GetUserQuery = ReasonApollo.CreateQuery(GetUser);
let usersQuery = GetUser.make(~pk="1", ());
<ReasonApollo.Provider client=Client.instance>
    <GetUserQuery variables=usersQuery##variables>
         ...(({result}) => {
         switch result {
              | NoData => <div>(str("No Data"))</div>
              | Loading => <div>(str("Loading"))</div>
              | Error(error) => (Js.log(error));<div></div>
              | Data(response) => (Js.log(response##_User##username));<div></div>
            }
        })
    </GetUserQuery>
</ReasonApollo.Provider>

Is there something that I'm missing?

Pattern matching for a mutation result is not correct

Issue Labels

  • has-reproduction
  • bug
  • blocking

I noticed that the pattern matching for the mutation result is not quite correct.

https://github.com/apollographql/reason-apollo/blob/master/src/graphql-types/ReasonApolloMutation.re#L63-L67

switch (
  apolloData##called |> Js.to_bool,
  apolloData##loading |> Js.to_bool,
  apolloData##data |> ReasonApolloUtils.getNonEmptyObj,
  apolloData##error |> Js.Nullable.to_opt
) {
| (true, false, _, _) => Called
| (_, true, _, _) => Loading
| (false, false, Some(data), None) => Data(Config.parse(data))
| (false, false, _, Some(error)) => Error(error)
| (false, false, None, None) => NoData

Mutation execution flow

https://github.com/apollographql/react-apollo/blob/master/src/Mutation.tsx#L165-L179

The initial state is

{
  loading: false,
  called: false,
  error: undefined,
  data: undefined,
}

When the mutation is being executed, the state transitions first to: (onStartMutation)

{
  loading: true,
  called: true,
  error: undefined,
  data: undefined,
}

Then we have either onCompletedMutation or onMutationError. In both cases the loading is set to false and either data or error are filled up.

โš ๏ธ However, called still remains to true

Pattern matching problem

So if we look at the pattern matching, we clearly see that Data, Error and NoData assume called to be false, which is wrong. Additionally, a variant for the "initial" state is also missing in my opinion.

Proposed solution

We should add a new variant called e.g. NoCalled (which reflects the initial state). The pattern matching should be then fixed as following:

| (false, false, _, _) => NotCalled
| (true, false, Some(data), _) => Data(Config.parse(data))
| (true, false, _, Some(error)) => Error(error)
| (true, false, None, None) => NoData
| (true, _, _, _) => Called
| (_, true, _, _) => Loading

Response includes field name that starts with uppercase letter

Description

Not able to access field on nested object when object contains field names that start with an uppercase letter. The response from the API looks like this:

{
  "data": {
    "Link": {
      "id": "cji9huoi5ob0o01032yhfsz9b",
      "title": "News from Indian Country"
    }
  }
}

In my query component I have:

Data(response) =>

If I try to access the link title I would do:

response##Link##title

However, I get this error:

[bucklescript] Js object ## expect syntax like obj##(paint (a,b))

I looked at transformers in the Apollo client docs but can't figure out how I would do it here to modify Link to be link instead. If anyone has a solution it would be greatly appreciated.

Related Issue

rescript-lang/rescript-compiler#2066

Client 2.1 Implementation

With ReactApollo 2.1 release, the main client is now very much usable for ReasonReact.

<Query/>
<Mutation/>

What is this projects desire to move to that implementation?
I took some time this morning to POC what this might look like. I started it as a new project at:
https://github.com/zenlist/bs-react-apollo

I used this project as a starting point(code is not currently properly credited).

So, the issues/questions.
Is this worth continuing?
Is there already an effort underway by this community?
Should this be done as a branch to this project or kept separate?

ReasonApolloQuery willRecieveProps will not fire a sendQuery

I am trying to update my render whenever the user changes a filter term.

let make = (~filter="", _children) => {
  ...component,
  render: _self => {
    let snippetQuery = SnippetQuery.make(~filter, ());
    <PageFrame>
      <Query query=snippetQuery>

The Query element re-renders on change but the graphql response does not. I believe this issue is caused by:

willReceiveProps: ({state, reduce}) => {
  if(!shallowEqual(asJsObject(q##variables), asJsObject(state.variables))) {
    sendQuery(~query=q, ~reduce);
    state;
  } else {
    state;
  }
},

The sendQuery will never fire from a change in variables, the comparison on JavaScript objects does not appear to work in the way intended for this if block.

For example, If you run this in https://reasonml.github.io/en/try.html the output will be false no matter what the input objects are.

type variables = {. "filter": string};
let x = {"filter": "test"};
let y = {"filter": ""};

Js.log(x != y);

Hopefully I am just using the system wrong but, if not, I am willing to make a PR to fix this issue.

graphql_ppx 0.2.2 is a breaking change

After updating to graphql_ppx 0.2.2 all of my graphql code failed to compile with an error about missing argument query.

The graphql_ppx change from 0.2.1 to 0.2.2 is says it "rename(d) "query" variable to avoid name clashes" so I think this is the breaking change.

Wrong type for generatedApolloClient methods

The return types for both query and mutate are string here
This renders ApolloConsumer a bit problematic to use. Is there a reason for the types. It might be that I just don't understand how to use it correctly.

Allow ApolloClient to be more configurable

Hey guys,

first of all thanks a lot for having some basic bindings of Apollo.
I'm just getting started with Reason and I'm trying it out in a current project of mine, so I'm still new to a lot of things and I'm still trying to understand how everything works.
Having already some bindings and code to look at is helping me a lot to figure out things.

I also really like the declarative components approach taken here instead of the classic HoC of react-apollo.

With that said, I'd like to know what are the plans for this library. For example, at the moment when you create a new instance of Apollo Client you can only pass the uri for the http-link.
Most of the time you also need other links (e.g. auth header, error, ...).
It would be nice to have this more configurable, e.g. you can easily create other links (given their bindings) and pass them to Apollo Client constructor.
Furthermore to also make things like e.g. InMemoryCache configurable.

For now I was able to do that myself by basically creating a new ApolloClient module and overriding some things, but it would be nice to have this out of the box.

If you'd like I can also try to contribute, probably with some help needed ๐Ÿ˜‡

Thanks a lot ๐Ÿ™

Query State Inconsistent

When you create a component, the initial variables are set into state. Theses are then checked as the incoming properties change to determine if the query should be re-executed.

However, the state is never updated. So, if you change the variables, then somehow get back to the original "variables" you will not get the correct results as the query will not re-execute. And any subsequent variables will always cause a re-query.

Suggestion:
In Query -> willReceiveProps

if (! shallowEqual(asJsObject(q##variables), asJsObject(state.variables))) {
        sendQuery(~query=q, ~reduce);
        {...state, variables: q##variables};
      }

This will allow it to execute properly.

PS Might be more worth the time/effort to start defining 2.1 support instead.

Using CreateQuery to get data only.

This library is intended for use of react-apollo in reason. Should I be writing additional bindings to just query data without sending it to the UI?

For example, using the swapi example in feature/react-apollo2.1

Can I do something like:

module GetAllPersons = [%graphql
  {|
    query getAllPersons {
      allPersons {
        id
        age
        name
      }
    }
|}
];
let client = Client.apolloClient(apolloClientOptions);

let personsQuery = GetAllPersons.make();

let query = personsQuery##query;

Js.log(query);

let persons = gql({|
    {
        query
    }
|});

let options = {"query": persons};

let testing =
  Js.Promise.(
    resolve(client##query(options))
    |> then_(response => {
         let data = response##data;
         Js.log2("TESTING", data);
         resolve();
       })
  );

Thanks.

Make graphql_ppx optional?

I don't want to make my devs get opam up and running on their machines to install graphql_ppx - I'd like to rely entirely on yarn. Is there any way to make the graphql_ppx dependency optional?

updateQuery for fetchMore

Issue Labels

  • has-reproduction
  • feature
  • docs
  • blocking
  • good first issue

I'm not sure if I'm missing something here, but it seems like fetchMore queries require an updateQuery function to be passed to them to determine how to get the new set of results based on the original query and the update. All of the examples here use them: https://www.apollographql.com/docs/react/features/pagination.html .

Is there some other way to achieve this with reason-apollo or is it just missing the from the type and bindings?

Also, my application throws this error:

Error: updateQuery option is required. This function defines how to update the query data with the new results.

Fails to find graphql schema

Issue Labels

  • has-reproduction
  • feature
  • docs
  • blocking
  • good first issue

Graphql_ppx.Schema_file_not_found
File "/Users/blackxored/Code/src/github.com/blackxored/esk/src/core/Apollo.re", line 1:
Error: Error while running external preprocessor

I've downloaded graphql_schema.json and I've put it everywhere in my project, root, next to the Apollo.re, parent dir.

apolloLinkErrorResponse##networkError confusing typing

Hi there!
I am trying to get networkError##statusCode in error link function:

let errorLink = {
  let errorHandler =
      (errorResponse: ReasonApolloTypes.apolloLinkErrorResponse) => {
    let networkError: option(ReasonApolloTypes.networkError) = errorResponse##networkError;
    switch networkError {
      | Some(error: ReasonApolloTypes.networkError) => Js.log(error##statusCode);
      | None => ()
    }
  };
  ApolloLinks.createErrorLink(errorHandler);
};

This code compiles quite fine but in runtime, error get undefined value, and error Uncaught TypeError: Cannot read property 'statusCode' of undefined occurs.

What's wrong with this code? And how to get statusCode?

Integrating with graphql_ppx

I have tried to integrate reason-apollo with graphql_ppx for a better dev experience like auto linting the query, auto type definition,...

This is what I've done so far https://github.com/thangngoc89/reason-apollo

It's a fork of this repo and maintaining a fork isn't something I want to do. So I hope it could be somehow make it back to this repo

[Examples] How to re-use existing JS-created Apollo client (and vice-versa)

Since integrating with existing projects is such a strong focus for Reason, it'd be great to have an example in the README to cover the case where my apollo client is created in JS-land and I want to use that from Reason-land.

Also vice-versa, in case that has any benefits, would be good to show how to share the Reason-created apollo-client with the JS components that expect it to be available.

Also asking because I need this and I'm not sure how to go about it!

With (Reason) react-native, the bundler dies with missing .js files

I am using this with reason / react-native (ios). The README installation instructions allow me to compile my graphql client fine with no type errors. But when I run the bundler (npm start -- I use the create-react-native-app starter) the bundler fails and I get failure red-screen due to missing javascript files.

  • apollo-link-ws
  • subscriptions-transport-ws

Adding these two manually with npm install gets past that red-screen to a successful bundle.

Configuration for GraphQL subscriptions

I am reading documentation for this graphql subscriptions network interface for apollo called subscription-transport-ws and it advises to install it in the following manner:

import { SubscriptionClient } from 'subscriptions-transport-ws';
import ApolloClient from 'apollo-client';

const GRAPHQL_ENDPOINT = 'ws://localhost:3000/graphql';

const client = new SubscriptionClient(GRAPHQL_ENDPOINT, {
  reconnect: true,
});

const apolloClient = new ApolloClient({
    networkInterface: client,
});

I don't see the networkInterface property supported by ReasonApollo.createApolloClient, however. I'm pretty new to Reason, so I feel I must be missing something. Is it possible to configure the networkInterface property when creating an apolloClient?

Issue Labels

  • has-reproduction
  • feature
  • docs
  • blocking
  • good first issue

bs-platform 4.0.0

Hi team.

Do you have a timeframe for bs-platform 4.0.0? We started to upgrade and noticed you are pinned to 3.

Thanks

SSR?

Are there plans to get this to work for server-side rendering?

ReasonReact deprecated self.reduce

Issue Labels

  • has-reproduction
  • feature
  • docs
  • blocking
  • good first issue

With latest [email protected] willReceiveProps only uses send instead of the old reduce,

  We've found a bug for you!
  .../node_modules/reason-apollo/src/graphql-types/ReasonApolloQuery.re 68:34-39

  66 โ”†     })
  67 โ”†   },
  68 โ”† willReceiveProps: ({state, reduce}) => {
  69 โ”†   if(!shallowEqual(asJsObject(q##variables), asJsObject(state.variable
       s))) {
  70 โ”†     sendQuery(~query=q, ~reduce);

  This record expression is expected to have type
    ReasonReact.componentSpec('a, 'b, 'c, 'd, 'e)
  The field reduce does not belong to type ReasonReact.self

Parse result of mutation in the promise

Issue Labels

  • has-reproduction
  • feature
  • docs
  • blocking
  • good first issue

It would be helpful if inside the promise result of the mutation function the result is returned already parsed just like the result that is passed as the argument of the render prop. Looking at the Pokemon example from README:

module AddPokemon = [%graphql
  {|
  mutation addPokemon($name: String!) {
      addPokemon(name: $name) {
          name
      }
  }
|}
];

module AddPokemonMutation = ReasonApollo.CreateMutation(AddPokemon);

let make = _children => {
  render: (_) =>
    <AddPokemonMutation>
      ...(
           (mutation /* Mutation to call */, 
           {result} /* Parsed result of your mutation */ ) => {
             let newPokemon = AddPokemon.make(~name="Bob", ());
             <div>
               <button
                 onClick=(
                   _mouseEvent =>
                     mutation(
                       ~variables=newPokemon##variables,
                       ~refetchQueries=[|"getAllPokemons"|],
                       (),
                     )
                     |> Js.Promise.then_(rawResult => {
                          /* The following line is required since the rawResult is not already parsed */
                          let parsedResult =
                            AddPokemonMutation.convertJSInputToReason(rawResult).result;
                          switch (parsedResult) {
                          | Loading => ()
                          | Error(err) => Js.log2("Error", err)
                          | Data(data) => Js.log2("Data", data)
                          };
                          Js.Promise.resolve();
                        })
                 )>
                 (ReasonReact.string("Add Pokemon"))
               </button>
             </div>;
           }
         )
    </AddPokemonMutation>,
};

Please let me know if you have any questions... thanks!

Fix the guide in README

I have moved away from my fork to main stream reason-apollo but I found the README guide is quite outdated and unusable. Here is a working one (directly from my project) :

module InMemoryCacheConfig = {
  type dataObject = {
    .
    "__typename": string,
    "id": string
  };
  let inMemoryCacheObject =
    Js.Nullable.return({"dataIdFromObject": obj => obj##id});
};

/* Create an InMemoryCache */
module Cache = ApolloInMemoryCache.CreateInMemoryCache(InMemoryCacheConfig);

let isProd = Js.to_bool([%bs.raw {|process.env.NODE_ENV === "production"|}]);

let uri = isProd ? "http://localhost:3000/graphql" : "/graphql";

/* Create an HTTP Link */
let httpLink = ApolloLinks.createHttpLink(~uri, ());

module Client =
  ReasonApollo.CreateClient(
    {
      let apolloClient =
        ReasonApollo.createApolloClient(
          ~cache=Cache.cache,
          ~link=httpLink,
          ()
        );
    }
  );

Better Error Information

Whenever there is an error with a Mutation, all I get is a message saying 'an error occurred'.

Would it be possible to simply pass the error returned from the Apollo Client mutation back so that it's easier to debug issues? I'm relatively new to a lot of this, so have no idea where to start debugging without proper error messages.

How to use client.query without JSX components?

I want to do an initial query on a reducer component's didMount function, which should just update the component's state with the result and not render anything. This would be closest to this behavior in ApolloClient

client
  .query({
    query: gql`
      {
        rates(currency: "USD") {
          currency
        }
      }
    `
  })
  .then(result => console.log(result));

found here https://www.apollographql.com/docs/react/essentials/get-started.html
I don't see an example of how to do this in the docs. Is there a reason that all of the examples in the docs are JSX components, and there are no imperative usages of Client's query function? (i.e. is that an antipattern?)

Issue Labels

  • has-reproduction
  • feature
  • docs
  • blocking
  • good first issue

Uncaught Error: You must wrap the query string in a "gql" tag.

I'm not sure if I'm doing something wrong but I can't get the graphql_ppx update to work. Throws error in console:

Uncaught Error: You must wrap the query string in a "gql" tag.
    at QueryManager.query (http://localhost:3333/index.js:40843:19)
    at ApolloClient.query (http://localhost:3333/index.js:40327:34)
    at sendQuery (http://localhost:3333/index.js:41814:57)
    at newrecord.(anonymous function) (http://localhost:3333/index.js:41843:9)
    at Object._1 (http://localhost:3333/index.js:543:12)
    at Object.componentDidMount (http://localhost:3333/index.js:1293:117)
    at Object.chainedFunction [as componentDidMount] (http://localhost:3333/index.js:37557:11)
    at commitLifeCycles (http://localhost:3333/index.js:25512:24)
    at commitAllLifeCycles (http://localhost:3333/index.js:26688:9)
    at HTMLUnknownElement.callCallback (http://localhost:3333/index.js:17284:14)
index.js:26489 
The above error occurred in the <ReasonApollo> component:
    in ReasonApollo (created by App)
    in App (created by Home)
    in div (created by Home)
    in Home
    in div (created by App)
    in App

Here's my repo: https://github.com/drejohnson/reason-apollo-graphql_ppx-test

I also get this warning in terminal:

Warning number 102
[0]   /home/dre/workspaces/PHRESHR/reason-apollo-graphql_ppx-test/node_modules/reason-apollo/src/graphql-types/ReasonApolloQuery.re64:11-43
[0]
[0]   62 โ”†   },
[0]   63 โ”† willReceiveProps: ({state, reduce}) => {
[0]   64 โ”†   if(q##variables != state.variables) {
[0]   65 โ”†     sendQuery(~query=q, ~reduce);
[0]   66 โ”†     state;
[0]
[0]   polymorphic comparison introduced (maybe unsafe)

How to create infinite loading component

I've been looking for create an infinite loading component. But it's very hard to do it with the current declarative API. I ended up cloning reason-apollo (again) to implement such feature. So I want to open an issue to discuss with you guys first before working on a PR. I see 2 possible ways to solve this:

  1. Implement fetchMore like react-apollo. This adds complexity to the project
  2. Provide an imperative API so this kind of feature could be implemented in userland

How to pass optional parameters?

I have a mutation that look like this:

datasetCreate(input: DatasetCreateInput!): DatasetCreatePayload

With input:

dataType: DataType!
description: String
isPrivate: Boolean
license: String
name: String!
sourceUrl: String
storagePath: String

In the client, I only want to fill the required fields, but the only solution that I found was to assign None to the optional fields. The problem is that using None will send the query with the field set to null. That's not the behavior that I want.

How can I build my query so that it does not send the fields I don't specify?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.