Skip to content
This repository has been archived by the owner on Apr 13, 2023. It is now read-only.

React Hooks support #2539

Closed
vovacodes opened this issue Oct 26, 2018 · 132 comments
Closed

React Hooks support #2539

vovacodes opened this issue Oct 26, 2018 · 132 comments
Assignees

Comments

@vovacodes
Copy link
Contributor

I'd like to start a discussion about the possible next-gen API based on react hooks for react-apollo.
I think this API brings a perfect opportunity for react-apollo to improve already excellent DX even more.

// isn't this beatiful?
function Avatar() {
  const { loading, error, data } = useQuery(`
    {
       me {
          initial
       }
    }
  `);

  if (loading) return <div>Loading...<div>;

  if (error) return <div>Error happened</div>

  return (
    <div>{data.me.initial}<div>
  );
} 

What do you guys think?

@vovacodes
Copy link
Contributor Author

Looks like the first step to make it happen is to start using the react 16.3 context API, I opened #2540 to address this

@kristianmandrup
Copy link

Yes, the Mutation and Query tags should also be available as useMutation and useQuery :)
Let's do it!

@kristianmandrup
Copy link

kristianmandrup commented Oct 27, 2018

I started a very basic attempt at hooks here: https://github.com/kristianmandrup/react-apollo/tree/hooks

Feel free to continue this effort. So far extracted Query and Mutation as classes so they are not React Components any longer but can be used as standalone classes.

Also added dependencies to 16.7 alpha versions of react and react-dom

    "react": "^16.7.0-alpha.0",
    "react-dom": "^16.7.0-alpha.0",

@lifeiscontent
Copy link

I also opened up this issue here. should I close it? apollographql/apollo-feature-requests#64

@vovacodes
Copy link
Contributor Author

@lifeiscontent no idea, would love to hear from the maintainers. it makes sense to close one of the issues to keep the discussion in one place, but I don't really know which place is the right one

@kristianmandrup
Copy link

The main effort lies in updating the tests to test the hooks. The implementation itself is mostly just wrapping existing functionality with a few changes. I did much of the basic work, but tests remain to be refactored which I've started.

See my hooks branch if you want to give it a go.

@kristianmandrup
Copy link

kristianmandrup commented Oct 27, 2018

I now have ~70% of the grunt work done here. Should be able to get it fully working and all tests passing, with ~3-4 hrs more work... Would love to see someone fork it and continue. Over and out here: midnight in London.

@kristianmandrup
Copy link

Added usage docs and examples for current implementation attempt: Apollo Hooks

@aralroca
Copy link

I like a lot the proposal 😍

@parsadotsh
Copy link

Integration with Suspense will also be nice.

@albertorestifo
Copy link

@hyperparsa Suspense is not yet ready to be used with data fetching.

@trojanowski
Copy link
Contributor

I created sample hooks to use with Apollo Client: https://github.com/trojanowski/react-apollo-hooks.

@danielkcz
Copy link
Contributor

Even though Suspense is not ready for data fetching, isn't it like double work to do it with hooks first and then rewrite to Suspense? I mean if you look at this video from React Conf (time included) it's kinda clear that any data fetching won't be just a simple hook. Andrew even mentions Apollo there and that it should probably wait for a proper implementation.

@vovacodes
Copy link
Contributor Author

vovacodes commented Oct 30, 2018

@FredyC that's a very good point, that's why it would really be great if the maintainers would share their vision before we go nuts with our implementations and PRs :) I understand the excitement everybody feels about Hooks but we need to plan better what should the ideal API look like @jbaxleyiii

@prateekrastogi
Copy link

getting crazy for hooks. Can we resolve this issue on priority basis?

@danielkcz
Copy link
Contributor

danielkcz commented Oct 31, 2018

@prateekrastogi If you are so crazy, just use a package from @trojanowski but be warned it will most likely become obsolete and completely different.

https://github.com/trojanowski/react-apollo-hooks.

@prateekrastogi
Copy link

prateekrastogi commented Oct 31, 2018

@FredyC I did checked that package, and that was my only reservation about it. :-)

@maxguzenski
Copy link

maxguzenski commented Oct 31, 2018

I've made a very basic useQuery/useMutation that is working on my medium/large project (not in production yet)

https://gist.github.com/maxguzenski/f23f7bba4c726ea5956b17fd3917fa1c

@juank11memphis
Copy link

Any idea when would this be ready? Would be awesome to have an official react-apollo hooks implementation :)

@aralroca
Copy link

aralroca commented Nov 1, 2018

@juank11memphis I imagine after hooks will be more stable, not alpha 😑 However, I'm also looking forward for this

@danielkcz
Copy link
Contributor

danielkcz commented Nov 1, 2018

@juank11memphis @aralroca Do you realized this isn't actually about hooks? To get the most out of upcoming changes, Apollo should leverage Suspense and that works somewhat differently. Sure, you can use hooks instead of current render prop components, no harm in there and it will work (some working implementations are already done ⬆️ ). However, note that you cannot really suspend rendering with a hook. You have to handle loading states by checking loading prop.

@alan345
Copy link

alan345 commented Nov 2, 2018

Great article: Writing Custom React Hooks for GraphQL https://medium.com/open-graphql/react-hooks-for-graphql-3fa8ebdd6c62

@trojanowski
Copy link
Contributor

trojanowski commented Nov 2, 2018

Even though Suspense is not ready for data fetching, isn't it like double work to do it with hooks first and then rewrite to Suspense? I mean if you look at this video from React Conf (time included) it's kinda clear that any data fetching won't be just a simple hook. Andrew even mentions Apollo there and that it should probably wait for a proper implementation.

Isn't the base of suspense stable since React 16.6? At least the Suspense component is documented in the official React docs. I understood from the linked presentation that the unstable part is the react-cache library which will allow us to easily build suspense-compatible resources. By the base of suspense, I mean a possibility of throwing promises in the render method / functional components and React waits for them to be resolved and shows the fallback UI defined with the <Suspense /> component. @acdlite @gaearon Is there a possibility it will be changed?

@juank11memphis @aralroca Do you realized this isn't actually about hooks? To get the most out of upcoming changes, Apollo should leverage Suspense and that works somewhat differently. Sure, you can use hooks instead of current render prop components, no harm in there and it will work (some working implementations are already done ⬆️ ). However, note that you cannot really suspend rendering with a hook. You have to handle loading states by checking loading prop.

@FredyC

I don't think it's correct. You can throw a promise inside of a hook. My react-apollo-hooks seem to be suspense compatible (you actually have to use it with the <Suspense /> component). It's done in that line.

@hlehmann
Copy link

hlehmann commented Nov 2, 2018

@trojanowski for what I have seens with react-apollo-hooks, useRef() and other hooks are not saved when you throw the promise, breaking the app in some case.

@trojanowski
Copy link
Contributor

@hlehmann Could you please create an issue at https://github.com/trojanowski/react-apollo-hooks/issues with more details?

@dai-shi
Copy link

dai-shi commented Nov 4, 2018

This is kind of off topic, but I've been thinking how I could use hooks without waiting for official support.
I created a small wrapper library that transforms render props to hooks. See the example below.

import { useRenderProps, wrap } from 'react-hooks-render-props';

const QUERY_POSTS = gql`                           
query queryPosts {                                        
  posts {                                                 
    id
    text
  }
}
`;

const useApolloQuery = (query) => {
  const [result] = useRenderProps(Query, { query });
  const fallbackResult = { loading: true }; // XXX this is a limitation.
  return result || fallbackResult;
};

// yeah, we can't avoid wrapping...
const PostList = wrap(() => {
  const { loading, error, data } = useApolloQuery(QUERY_POSTS);
  if (loading) return <span>Loading...</span>;
  if (error) return <span>Error: {error}</span>;
  if (!data) return <span>No Data</span>;
  return (
    <ul>
      {data.posts.map(item => <li key={String(item.id)}>{item.text}</li>)}
    </ul>
  );
});

const ADD_POST = gql`                              
mutation addPost($text: String!) {                        
  addPost(text: $text)                                    
}
`;

const useApolloMutation = (props) => {
  const results = useRenderProps(Mutation, props);
  return results;
};

const NewPost = wrap(() => {
  const [addPost] = useApolloMutation({ mutation: ADD_POST, refetchQueries: ['queryPosts'] });
  const add = (text) => {
    addPost({ variables: { text } });
  };
  return (
    <TextInput add={add} />
  );
});

The full example is in: https://github.com/dai-shi/react-hooks-render-props/tree/master/examples/03_apollo

Note that this is an experimental project for fun.

@prateekrastogi
Copy link

Since apollo-react 2.3 released yesterday, are there any plans or timeline to give this feature request serious attention for future release?

@revskill10
Copy link

revskill10 commented Feb 24, 2019

Thanks @FreddieRidell for suggestion. I'll just keep fetch and cache the hashed query in Redux to rehydrate on client later.
@MrLoh I never say we don't need a client, i just say it should be hidden from api surface. Just in fetch, we just need to fetch(url, options) and no need to care what happens behind the scene.
In my component, i need to use multiple graphql endpoints, so this one requires overhead to keep track of multi clients, and i think it should be the job of a library. (What if i want to reuse those clients in other components ?)

@danielkcz
Copy link
Contributor

danielkcz commented Feb 24, 2019

In my component, i need to use multiple graphql endpoints, so this one requires overhead to keep track of them, and i think it should be the job of a library.

Have you ever heard about graphql stitching? :) That's generally what solves the "problem" of multiple endpoints, just join those together. It is indeed harder to solve this client side, so that's why there are tools to tackle it elsewhere more gracefully.

Nonetheless, this is getting off topic. You are happy with your choice and we are happy what Apollo does for us. It's a win-win ;)

@mbrowne
Copy link
Contributor

mbrowne commented Feb 24, 2019

It is indeed harder to solve this client side, so that's why there are tools to tackle it elsewhere more gracefully.

This is true, but actually handling this client side (if that's a requirement) is very doable with the features already provided by apollo-client and apollo-link. I would recommend that anyone interested in discussing that further check out the Spectrum community and maybe start a new thread there.

@oklas
Copy link

oklas commented Feb 24, 2019

@FredyC, if we will integrate as suggested two object or list queries into one request, it is impossible to refetch data for only one query in that request.

@rovansteen:

So I think it makes most sense to stick with the current objects until Suspense simplifies everything massively.

Yes we are currently discussing future api, hooks seems not yet merged. So array api may be like this:

  const [ dogs, dogsRefetch, { fetchMore: dogsMore } ] = useQuery(GET_DOGS)

It is like just functions. It may be actually considered as function params. It is just params of callback function but converted to return value. Too much functions have some position parameters and object parameters. for example, not need to go so far, fetch itself:

  fetch('/endpoint', { method: 'GET', headers })

The question is: is it possible to extract some small amount of API as frequently used to position parapeters in tuple?

@oklas
Copy link

oklas commented Feb 24, 2019

The way to use namespacing by prefix in object form:

const { lecturer, lecturerRefetch } = useQuery(GET_LECTURER, {prefix: 'lecturer'})
const { studentList, studentListRefetch } = useQuery(GET_SDUDENTLIST, {prefix: 'studentList'})

@danielkcz
Copy link
Contributor

danielkcz commented Feb 24, 2019

@oklas So because of some theoretical need to run multiple queries in a single component you would have botched the API like that? 😮 Seriously, how many components with multiple useQuery have you seen so far?

We are in the middle of refactoring some medium sized app and there was not a single occasion to need that. If more data is needed, we rather make a single merged query which is way easier to work with (single loading, error...).

Sure, there might be some cases where one query depends on another, but I am in favor of separating that into another nested component that receives data from the previous one through props. SRP is a thing :)

@oklas
Copy link

oklas commented Feb 24, 2019

Any component which manage relations between more then one different objects

@danielkcz
Copy link
Contributor

danielkcz commented Feb 24, 2019

The way to use namespacing by prefix in object form:

const { lecturer, lecturerRefetch } = useQuery(GET_LECTURER, {prefix: 'lecturer'})
const { studentList, studentListRefetch } = useQuery(GET_SDUDENTLIST, {prefix: 'studentList'})

This would be a nightmare for the TypeScript/Flow world... 😱

Yea, as I said theoretical until proven otherwise and even then it's upon heavy consideration if that couple of rare cases is worth redesigning API and making it harder for others.

@MrLoh
Copy link
Contributor

MrLoh commented Feb 24, 2019

A quite common and very valid use-case for several queries in one component ist query splitting (see docs here).

We have that in our app in a few places. A list view renders items with a little data, then if an item is clicked, the detail view uses a query that gets the data that’s already in cash to display it and a second query to load more data over the network. This is super ugly with HOCs as you can see in the docs.

I actually like the list return from hooks as it reflects how useState works, but as already mentioned it will be easy to customize with your own hook. Probably we will refactor all data fetching code out of our components into shared custom hooks that will get data and can be reused to more easily optimize things like query splitting.

@danielkcz
Copy link
Contributor

danielkcz commented Feb 24, 2019

Interesting, I always thought that Apollo is slightly smarter in this and it actually requests only data that are not in cache (unless fetchPolicy dictates otherwise). I was never curious enough to investigate it 😅 Ok, that is certainly a valid case, but still, there isn't probably many of those.

Either way, the point is that the current API is not preventing anyone from running multiple queries. It's just slightly less convenient/verbose to do it. And if someone does not like it, there is indeed a super easy way of abstracting it with a custom hook.

function useTupleQuery(...) {
  const { data, loading, error, ...rest } = useQuery(...)
  return [ data, loading, error, rest ] 
}

It's so tiny that there is no problem to have it copy-pasted in userland code. The beauty is that you can easily choose which variant to use based on the scenario in the component. You can even mix both. Sure, even if API would be other way around, we could do the opposite. The main reason why I don't like that is that the tuple variant will be always more opinionated due to an order of arguments which can be a source of weird errors.

@CrocoDillon
Copy link

The main reason why I don't like that is that the tuple variant will be always more opinionated due to an order of arguments which can be a source of weird errors.

This ^ Imagine if the Query render prop would use tuples

<Query {...props}>
  {([i, dont, know, the, order]) => <Thing />}
</Query>

That looks so weird, but now with hooks it's suddenly different. Maybe the best thing is just to keep the API similar to the Query render prop so you don't even need to think about destructure order.

@DanielFGray
Copy link

DanielFGray commented Feb 26, 2019

As far as bikeshedding the API of the tuple goes, I think to align with the native hooks,

const [{ data, loading, error, ...rest }, refetch] = useQuery(...)

makes the most sense, considering most are in the form of [state, changeState].

@revskill10
Copy link

revskill10 commented Feb 26, 2019

For anyone interested, here's the gist i'm using: https://gist.github.com/revskill10/d313f30771b8eeb2bc423867c300fe9c
with the API i suggest above.
All graphql operation has this form:

const [action, data, loading, error] = useFetchQL(
  {key, query, mutation, subscription,...}, callback
)

Surprisingly, there's no difference at all between query, mutation, or subscription
key is the key in the cache map.
Callback is called after operation completed.
action is the action, for query it's the refetch, for mutation, it's the mutation, for subscription, it's the subscribe
It's Suspense ready, i tested with SSR suspense, too.
Demo is here , with SSR, caching, realtime in one component.
For subscription, i'm temporarily using apolloClient though, but disable its cache. Will replace with native websocket implementation later. Query and Mutation just use native fetch.

Here's another use of useFetchQL,

const [data] = useFetchQL({
    key: 'top-10-posts',
    fetch: async () => {
      const url = 'https://jsonplaceholder.typicode.com/posts?_page=1&_limit=10'
      return await fetch(url)
    }
  });

It supports generic async call, too.

So, let's just see that, don't treat graphql at any difference with another. From the consumer point of view, Graphql is just a string inside your body string, treat it no difference to another , old school fetch that we used and loved in the past.

@oklas
Copy link

oklas commented Feb 26, 2019

Easy expansion of the existing API with format-like method(s):

const [data, refetch] = useQuery(...).asState()

@danielkcz
Copy link
Contributor

danielkcz commented Feb 26, 2019

@oklas I don't think it's even worth considering to include that in the core. Same reason why React does not include hundreds of utility hooks. Everyone has different needs and opinions on how API should look like. It's great we have custom hooks and you can shape it however you like without bothering others with it. Let's stop this discussion as it doesn't really lead to any useful end.

@fbartho
Copy link

fbartho commented Feb 26, 2019

@FredyC if what you’re saying is: react-Apollo should have an official hooks implementation, but that we should stop quibbling as to the exact syntax, because anybody can wrap that to their liking. Then I agree!

Nearly any official react-apollo hooks strategy is enough here!

@jigsawye
Copy link

Maybe the return value can try this:

function useQuery() {

  // ...

  const ret = [data, loading, error]; // or something else
  ret.data = data;
  ret.loading = loading;
  ret.error = error;
  // or something else

  return ret;
}


const [data, loading, error] = useQuery(); // work
const { data, loading, error } = useQuery(); // also work

@sessionboy
Copy link

sessionboy commented Feb 27, 2019

This is very good:

const [{ data, loading, error, ...rest }, refetch] = useQuery(...);

If the array has more than two elements, it's going to be a little tricky. For example, I just want one and four:

// Get all :
const [data, loading, error,refetch] = useQuery();

// Get one and four. This is bad
const [data, , ,refetch] = useQuery();

@slikts
Copy link

slikts commented Feb 27, 2019

Valid syntax for eliding members when destructuring is:

const [data, , , refetch] = useQuery();

@danielkcz
Copy link
Contributor

danielkcz commented Feb 27, 2019

Please, let's leave these ideas of tuples. The useState has kinda opened hell gate and now everything needs to a be a tuple while before there weren't many people who wouldn't think about such an approach. It certainly has its appeal for some use cases, but seriously, for different arguments is a total nightmare. Who is going to decide what's the "correct" order? Are you always going to remember what's the order?

I don't really follow why is so hard for you to just do this in your app/lib code. It's an absolute win-win for everyone.

function useTupleQuery(...) {
  const { data, loading, error, refetch, ...rest } = useQuery(...)
  return [ data, loading, error, refetch, rest ] 
}

@ryandrewjohnson
Copy link

ryandrewjohnson commented Feb 27, 2019

@hwillson Thoughts on locking this thread and moving these other discussions over to apollo's official Spectrum channels? I'm subscribed to this thread to receive updates on the official hooks implementation, and these other convos are blowing up my notifications. Sure I can unsubscribe, but than I lose visibility on the updates I'm really here to receive like:

#2539 (comment)

@bmullan91
Copy link

First off kudos to the Apollo team with the updates and clear roadmap. I've used Apollo on a few projects and it's worked great.

We've been working on a project graphql-hooks. It's a super lightweight GraphQL client with first class hooks support.

We'd love to get some feedback, and if anyone would like to get involved please get in touch!

PS: great job on react-apollo-hooks @trojanowski

@maxlew
Copy link

maxlew commented Feb 27, 2019

We use multiple GraphQL queries in a single component quite a lot in our large app.
However I agree with @FredyC - ours is a super niche situation and considering our projects complexity it's not too much to expect us to create our own useTupleQuery wrapper. Having any kind of spec is better than arguing about API implementations.

@revskill10
Copy link

I sketch out basic implementation here
Basically, it has the following API:

const [{
  json, error, loading
}, {
  refetch, abort, setQuery, setTimeout, setVariables, setInit, setOperationName
}] = useGraphql({
  key, url, query, variables, operationName, init, skip, timeout
}, {onComplete, onError, onAborted})

The benefit is, we can easily change everything we want, from query, timeout, variables, headers, operation name in one component. With this, a custom GraphiQL is easily done !

@danielkcz
Copy link
Contributor

danielkcz commented Feb 28, 2019

@revskill10 Seriously man, no offense, but nobody cares about your "I use graphql like a fetch", so please stop spamming with it and spreading it like some holy grail. If you really like it, make your own lib and perhaps you will be famous, but it's not funny anymore.

@MrLoh
Copy link
Contributor

MrLoh commented Mar 1, 2019

@hwillson please lock the thread!

@apollographql apollographql locked and limited conversation to collaborators Mar 1, 2019
@hwillson
Copy link
Member

hwillson commented Mar 1, 2019

There has been a lot of great discussion here, but this thread is definitely getting a bit unwieldy. We'll lock it for now, and post back when we have updates to share. Let's keep the discussion going over in https://spectrum.chat/apollo. Thanks!

@hwillson
Copy link
Member

Hooks support is almost ready. Anyone interested can follow along in #2892. We'll be merging into master very shortly, and releasing as soon as we get the docs updated. Thanks all!

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

No branches or pull requests