Skip to content
This repository has been archived by the owner on Nov 20, 2020. It is now read-only.

Proposed API change #49

Merged
merged 9 commits into from
Oct 24, 2019
Merged

Conversation

jfrolich
Copy link
Member

@jfrolich jfrolich commented Sep 18, 2019

This is a WIP, but I found that the functor API created quite a bit of boilerplate code. When I looked at the source of reason-urql, it found a way to implement a more concise API, so I messed around with the source of reason-react-hooks if we can have something like that that with apollo and this is the result. I would like to get your opinion and possible improvements.

In short we can go from this:

module ActivityPhotosQueryConfig = [%graphql
  {|
  query ActivityPhotos($feedId: ID, $scaleFactor: Float!, $height: Float!) {
    feedImageList(feedId: $feedId) {
      nodes {
        id
        width
        height
        file(scaleFactor: $scaleFactor, height: $height) {
          url
        }
      }
    }
  }
|}
];
module ActivityPhotosQuery =
  ReasonApolloHooks.Query.Make(ActivityPhotosQueryConfig);

[@react.component]
let make = (~feedId=?, ~small=false, ~edit=false) => {
  let height = small ? 100. : 200.;
  let (query, _full) =
    ActivityPhotosQuery.use(
      ~variables=
        ActivityPhotosQueryConfig.make(
          ~feedId?,
          ~scaleFactor=PixelRatio.get(),
          ~height,
          (),
        )##variables,
      (),
    );
}

to this:

module ActivityPhotosQuery = [%graphql
  {|
  query ActivityPhotos($feedId: ID, $scaleFactor: Float!, $height: Float!) {
    feedImageList(feedId: $feedId) {
      nodes {
        id
        width
        height
        file(scaleFactor: $scaleFactor, height: $height) {
          url
        }
      }
    }
  }
|}
];


[@react.component]
let make = (~feedId=?, ~small=false, ~edit=false) => {
  let height = small ? 100. : 200.;
  let (query, _full) =
    ReasonApolloHooks.useQuery(
      ~query=
        ActivityPhotosQuery.make(
          ~feedId?,
          ~scaleFactor=PixelRatio.get(),
          ~height,
          (),
        ),
      (),
    );
}

Similar for mutations, however we need to pass in an initial mutation if we want to provide variables later:

let (create, _simple, _full) =
  ReasonApolloHooks.useMutation(
    ~incompleteMutation=CreateFeedImageMutation.query,
  (),
);

We can run the create function like this:

create(
  ~mutation=CreateFeedImageMutation.make(~feedImage, ()),
  ~refetchQueries=_ => [|refetchQuery(~feedId?, ())|],
  ~awaitRefetchQueries=true,
  (),
)

or we can add the mutation from the get go (and then we don't have to supply it later):

let (create, _simple, _full) =
  ReasonApolloHooks.useMutation(
    ~mutation=CreateFeedImageMutation.make(~feedImage, ()),
  (),
);

To be honest the mutation API is suboptimal. I think there is a way that we do not need to provide the incompleteMutation but this involves changing the javascript useMutation hook.
OR we need to modify graphql_ppx, to generate a different module.

@fakenickels
Copy link
Member

Thanks for your proposal!
I think we can leave this opened for a while to see what others will say about it

@jfrolich
Copy link
Member Author

Sounds great. I am pretty new to Reason/OCaml, so there are probably many things that can be better.

@fakenickels
Copy link
Member

No opinions of otherss so far but in overall I liked the simplification.
Can you update the examples? After that I think we are good to go

@jfrolich
Copy link
Member Author

jfrolich commented Oct 8, 2019

I updated the PR, a few changes:

  • Changed the main module to ApolloHooks (debatable, but more concise it's possible now that the package is not namespaced anymore)
  • Updated graphql_ppx in the example to graphql_ppx_re (which is a maintained version of graphql_ppx in reason -- same API).
  • Removed the namespacing so we can do ApolloHooks.useQuery directly instead of ApolloHooks.Query.useQuery or something. Also have all the hooks available when opening ApolloHooks.

@fakenickels
Copy link
Member

fakenickels commented Oct 15, 2019

Thanks a lot @jfrolich ! These next upcoming weeks we'll be really busy for me but I'll try to review and merge ASAP

"css-loader": "^3.2.0",
"graphql_ppx": "^0.2.8",
"@baransu/graphql_ppx_re": "^0.2.0-beta.2",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very good!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah so cool to see activity again in this package. It has so much potential.

@jfrolich
Copy link
Member Author

Thanks a lot @jfrolich ! These next upcoming weeks we'll be really busy for me but I'll try to review and merge ASAP

Cool. No worries. When this is merged I also have a pull-request that allows to do fetchMore in pure reason, instead of the raw bucklescript escape hatch!

@sgrove
Copy link

sgrove commented Oct 23, 2019

This looks fantastic!

We've added code-gen support for the current API, you can see the current usage for both queries and mutations here: https://serve.onegraph.com/short/B8DR68 (open code exporter and choose Reason as the language)

I'd love to get this merged in and make the code that much more approachable for workshops (and for me too of course!).

@fakenickels
Copy link
Member

Amazing!! Will get this merged before weekend 🙏

@jfrolich
Copy link
Member Author

This looks fantastic!

We've added code-gen support for the current API, you can see the current usage for both queries and mutations here: https://serve.onegraph.com/short/B8DR68 (open code exporter and choose Reason as the language)

I'd love to get this merged in and make the code that much more approachable for workshops (and for me too of course!).

Wow that codegen is awesome!

@jfrolich
Copy link
Member Author

Amazing!! Will get this merged before weekend 🙏

I merged in the upstream changes and upgraded graphql_ppx_re in the example.

@fakenickels fakenickels merged commit 624ff24 into reasonml-community:master Oct 24, 2019
@fakenickels
Copy link
Member

Merged will release soon!

@fakenickels
Copy link
Member

Thanks lot @jfrolich for this!! Awesome work. And thanks everyone for giving feedback

@MargaretKrutikova
Copy link
Collaborator

I am a bit late to the party, but what happened to errorPolicy in useQuery? I think it disappeared.

@fakenickels
Copy link
Member

Looks like we have some regressions here indeed, along with #57

/**
* apollo-client/src/core/watchQueryOptions.ts
*/
type errorPolicy =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are those not used anymore?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah that is probably an upstream change that I forgot to merge in (diffs were not working anymore so I had to manually look up what has been added to master in the meantime)

@fakenickels
Copy link
Member

We haven't released this just yet so there is still time to fix

@MargaretKrutikova
Copy link
Collaborator

I will try to run the example project locally, and see what I can fix 😄

| (None, None) => raise(Error("Need to pass a mutation"))
};

React.Ref.setCurrent(parse, Some(mutation##parse));
Copy link
Collaborator

@MargaretKrutikova MargaretKrutikova Oct 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, this runs inside of useMemo? Callback passed into useMemo must be pure because it will run on every render, see react docs. And later it calls jsMutate which will send the actual query, is it correct?
I am not sure it is a good idea to implement it this way, I think we will have to rethink this approach a bit, what do you think, @fakenickels ?

Copy link
Collaborator

@MargaretKrutikova MargaretKrutikova Oct 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, my bad, spent some time alazying the code and realised that it is memoizing the function and not actually calling it 🤦‍♀ 😄 Maybe it makes sense to use useCallback instead of useMemo here?

Copy link
Member Author

@jfrolich jfrolich Oct 25, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I merged this change from master, but good suggestion. useCallback is just a shortcut for memoizing functions with useMemo if I remember correctly.
(edit: it actually memoizes the result and doesn't memoize the function, so useCallback is not applicable here)

edit2:

Callback passed into useMemo must be pure because it will run on every render, see react docs.

It does not run on every render, it only runs if the dependencies change.


let useMutation =
(
~incompleteMutation=?,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure incompleteMutation is the right name here, it might get really verbose and tedious to write on many mutations, can we come up with something more succinct for this case? 😄

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually you would use the mutation argument, unless you need to set the arguments dynamically. It would be better to have the mutation as an optional argument, but that needs some changes on the javascript side (I think!). Anyway I agree it's not ideal (and hopefully temporarily). Let me know what better name you are thinking of!

| (_, Some(incompleteMutation)) => incompleteMutation
| (None, None) =>
raise(
Error("You need to provide a mutation or an incomplete mutation"),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to have a better exception type, something more descriptive than Error, does it makes sense, @fakenickels ?

Copy link
Collaborator

@MargaretKrutikova MargaretKrutikova Oct 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can avoid throwing errors here by making sure the user can't make a mistake on the type level? I think it will be better to not throw errors if the type system can guarantee that the values are correct 😄 I was thinking about using some kind of a variant:

type mutationConfig('a) = Query(string) | Config('a);

and then have it in useMutation:

let useMutation =
    (
      ~mutation: mutationConfig('a)=?,
      ~client=?,
     ....
      (),
    )

And remove incompleteMutation, maybe something in that direction could be a more intuitive API?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally we can remove the need to pass in incompleteMutation in the future which will solve this issue.

@fakenickels
Copy link
Member

I think will be better to revert this and open the PR again so we can make we have less breaking changes

@fakenickels
Copy link
Member

@jfrolich would you mind reopening this PR?

@jfrolich
Copy link
Member Author

jfrolich commented Oct 25, 2019

Sorry about this. I could directly merge master because due to filename changes it wouldn't diff the fires. So I looked up the individual commits and applied them manually, but I messed up and missed two additions!

I will re-open this PR, and fix the issues as reported.

Some comments of @MargaretKrutikova are about existing code not related to this PR. This can be tackled in a separate PR. (Except the naming of incompleteMutation, happy to change this name if anyone has a name that is better suited or a way so we don't need to pass in the incomplete mutation.

@fakenickels
Copy link
Member

@all-contributors add @jfrolich code, review and ideas

@allcontributors
Copy link
Contributor

@fakenickels

I've put up a pull request to add @jfrolich! 🎉

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants