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?
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
Yes, the Mutation and Query tags should also be available as useMutation and useQuery :)
Let's do it!
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",
I also opened up this issue here. should I close it? https://github.com/apollographql/apollo-feature-requests/issues/64
@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
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.
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.
Added usage docs and examples for current implementation attempt: Apollo Hooks
I like a lot the proposal 😍
Integration with Suspense will also be nice.
@hyperparsa Suspense is not yet ready to be used with data fetching.
I created sample hooks to use with Apollo Client: https://github.com/trojanowski/react-apollo-hooks.
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.
@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
getting crazy for hooks. Can we resolve this issue on priority basis?
@prateekrastogi If you are so crazy, just use a package from @trojanowski but be warned it will most likely become obsolete and completely different.
@FredyC I did checked that package, and that was my only reservation about it. :-)
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
Any idea when would this be ready? Would be awesome to have an official react-apollo hooks implementation :)
@juank11memphis I imagine after hooks will be more stable, not alpha 😑 However, I'm also looking forward for this
@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.
Great article: Writing Custom React Hooks for GraphQL https://medium.com/open-graphql/react-hooks-for-graphql-3fa8ebdd6c62
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
loadingprop.
@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.
@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.
@hlehmann Could you please create an issue at https://github.com/trojanowski/react-apollo-hooks/issues with more details?
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.
Since apollo-react 2.3 released yesterday, are there any plans or timeline to give this feature request serious attention for future release?
My attempt was mostly just looking into what it would take to refactor the existing code to be more flexible in order to accommodate hooks or other api "consumers". Currently the code contains a lot of anti patterns (at least in my book), being hard coded for one particular consumer.
The code could use some fresh refactoring to be much more composable and loosely coupled IMO.
I'm fully aware that my "attempt" is not working. I never even tried to run it.
The problem is that the core code is fitted directly into a component, whereas a much better way would be to have it standalone and then add a thin component on top, to accommodate queries/mutations being used from the view layer as components (render props, HOC etc.) but allowing direct access for other exposers/consumers to the core logic without going through a component.
Any update on this?
@dennishansen What update are you expecting? If you are eager to play with hooks, just grab https://github.com/trojanowski/react-apollo-hooks? I think that top-level API won't be that different once implemented officially.
@FredyC Are we waiting on some particular release of the React Hooks API before we consider accepting PRs that would add Hooks support into the react-apollos core repo?
It'd be awesome if we could consider porting @trojanowski's work into core as a starting point.
I also believe that thinking about an official apollo hooks API is a good idea.
Once react hooks get to stable (sometimes in Q1 2019) people want to use them with apollo in production. This means the apollo hooks API shouldn't change then :)
Also: The time between now and then gives the community the possibility to give feedback on the API design.
I will be extremely happy with the alpha version that will use hooks, also I think it can give a nice PR effect and hype effect to react-apollo itself which is nice for an OSS project. So I think that would be win-win for everyone.
Please consider it. Thanks.
Hi all - we're definitely planning on implementing React Hooks support. We've been in discussions with the React core team over Hooks and Suspense, and while these new React features are super exciting, they're not quite ready to be implemented yet. When things stabilize a bit more, we'll be jumping on this for sure.
I'm fantastically interested in this feature. We re-wrote our whole app to React-Native over last year, and we heavily relied on Apollo. It shipped & is stable!
I did a quick experiment in our app, and react-hooks would significantly increase the legibility of many of our data containers (Screens/Controllers) -- as well as increasing team velocity due to the lower cognitive burden & easier diffing.
We could take a hybrid approach and use hooks for everything except for the Query-components, but using @trojanowski's lib, it really flattens and simplifies a lot of what's going on.
I have team resources for the next few months that I can apply to this, so we would love to contribute as-soon-as-possible. Please let us know!
Thanks very much for the offer to help @fbartho! We're discussing this internally at a meeting this upcoming Monday, after which I'll post back with a plan of attack.
I have been using @trojanowski lib for a bit as well and it's clear to me now that hooks version is not a silver bullet. There are basically two scenarios where it needs a bit different approach. Perhaps more, but I haven't found them yet.
Executing a query on request (by a user), like filter form which should run a query only after hitting the "filter" button. I basically made a custom useExecutableQuery which just uses client.query underneath, but it's not always ideal. Especially thinking in terms of Suspense, I opted to usual loading variable as I have much more control over what's displayed where. I hope this will get somehow improved in future.
Another scenario is a conditional query. The skip attribute (got merged yesterday) is one way but feels rather awkward. Rendering appropriate <Query> feels so much easier in the end.
Mutations and Subscriptions are winning with hooks for sure. Either way, it's just as a few considerations for future implementations, nothing spectacular :)
@hwillson Any news? Whats the plan of attack?
Sorry for the delay all (last weeks meeting was re-scheduled to today). We've had some preliminary discussions about React Apollo's future Hooks support, and have set aside more time to work on it. Our biggest Apollo Client / React Apollo push for the next 2 weeks is finalizing https://github.com/apollographql/apollo-client/pull/4155, and everything that's impacted by that PR (e.g. Apollo Client Devtools, Link state, docs, etc.). After AC 2.5 is published, we'll have more time to devote to Hooks work. In the meantime however, we're working on hashing out an initial design, which we'll share with everyone here for feedback. Once we're all happy with the design, then we'll go from there - if anyone here wants to help work on the code, then awesome! :bow:
Isn't the base of suspense stable since React 16.6? At least the Suspense component is documented in the official React docs
I don't think it's correct. You can throw a promise inside of a hook.
AFAIK the whole "throw a Promise" thing is not part of React's official API. At the moment <Suspense> can be only be used in combination with lazy.
@conradreuter Throwing a promise already works inside <Suspense> but it might be premature to start using it, since it's not official yet...for one thing, it doesn't seem to work with <ConcurrentMode> yet.
@conradreuter @mbrowne I found this in Reactiflux:

and I also found this tweet: https://twitter.com/acdlite/status/1056940697775878145. So I guess the "throw a promise" part of suspense can be considered safe to use.
An update to my last comment - @gaearon says that using Suspense for data fetching is not recommended yet: https://github.com/trojanowski/react-apollo-hooks/issues/69#issuecomment-460833136.
Hi guys! I'm really looking forward to Apollo Client Hooks :D
While I'm (anxiously) waiting, I have built my own for our use case (basically CRUD, create, retrieve, update and delete functionality) and some helper functions to make my life easier ;)
An extremeley naive implementation, that doesn't support SSR :(
import {useContext, useEffect, useState} from "react"; import {ApolloContext} from "../App"; import {CreateChildComponent, DeleteComponent, GetComponent, MutateComponent} from "./Site"; export const useComponent = (id) => { const [data, updateData] = useState({}) const [currentData, updateCurrentData] = useState({}) const {apolloClient: client} = useContext(ApolloContext) || {} const saveComponent = (newVariables) => { const variables = mixVariables(currentData, newVariables || currentData) client.mutate({ mutation: MutateComponent, variables: {id, ...variables}, refetchQueries: [{ query: GetComponent, variables: {id} }] }) updateCurrentData(JSON.parse(JSON.stringify(variables))) updateData(variables) } const changeComponent = (newVariables) => { if (typeof newVariables !== "object") { throw new Error("changeComponent must have a object argument!!!!!!!!!!!!") } const variables = mixVariables(currentData, newVariables) updateCurrentData(variables) } const resetComponent = () => { updateCurrentData(JSON.parse(JSON.stringify(data))) } const addChildComponent = (type, data) => { client.mutate({ mutation: CreateChildComponent, variables: {id,type, ...data}, update: (cache, {data: {createChildComponent}}) => { const { component} = cache.readQuery({ query: GetComponent, variables: {id} }); component.children.push(createChildComponent) cache.writeQuery({ query: GetComponent, data: { component}, variables: {id} }); updateCurrentData(JSON.parse(JSON.stringify(component))) updateData(component) } }) } const deleteComponent = () => { client.mutate({ mutation: DeleteComponent, variables: {id}, update: (cache, {data: {deleteComponent}}) => { const deletedComponentId = deleteComponent.id; const keys = Object.keys(cache.data.data); for (let i = 0; i < keys.length; i++) { const tmp = cache.data.data[keys[i]] if (tmp.__typename !== "Component") continue const deletedItem = tmp.children.find(item => item.id === 'Component:'+deletedComponentId) if (deletedItem) { const {component: parent} = cache.readQuery({ query: GetComponent, variables: {id: tmp.id} }); parent.children = parent.children.filter(child => child.id !== id) cache.writeQuery({ query: GetComponent, data: { component: parent}, variables: {id: parent.id} }); break; } } } }) } useEffect(() => { if (!id) { updateData({}) updateCurrentData({}) return; } client.query({ query: GetComponent, variables: {id} }).then(({data}) => { updateData(data.component) updateCurrentData(JSON.parse(JSON.stringify(data.component))) }) }, [id]) return [id ? currentData : {},changeComponent, saveComponent, resetComponent,deleteComponent, addChildComponent] } export default useComponent function mixVariables(currentData, {content, lang, order, tags}) { return { content: typeof content !== "undefined" ? content : currentData.content, lang: typeof lang !== "undefined" ? lang : currentData.lang, order: typeof order !== "undefined" ? order : currentData.order, tags: typeof tags !== "undefined" ? tags : currentData.tags, }; }
Also, can't wait for native Apollo hooks (did Isay this already?)
Personally I'm not in a rush to replace <Query /> components, <Mutation /> components however very quickly lead to render prop "christmas trees" when you have to add a 3 or more mutations to one particular data container, and I'm guessing most people have a similar experience.
Would it be an option to incrementally add hooks support by perhaps focusing on mutations first, or does that not make much of a different implementation-wise in the grand scheme of things?
I replaced react-apollo with react-apollo-hooks and everything works great in my project except subscriptions.
Opposed to @pleunv, I cant wait to get Hook support for queries and mutations in apollo.
The renderprop syntax is the only thing which holds be back from using apollo and graphql in all of my projects. I am rather using the plain JS graphql api for now, and load the data in componentDidMount.
Hooks gonna bring the API for frontend graphql to a new level, so please push that topic :)
@breytex You could still use the graphql hoc api
So now that Hooks are stable in React, I want to share here our full stack WhatsApp clone!
It uses React Hooks (updated to the new stable version), Suspense, Apollo, GraphQL Subscriptions, Typescript and also the whole backend (Postgres, Typescript, etc..) - mostly thanks to the great work from @trojanowski on react-apollo-hooks.
I wanted to write it here mostly because I feel there should be more comments on this thread about the amazing work @trojanowski is doing.
He is doing a fantastic job on his library, which led to a lot of great design discussions happening in the open (on issues on his repo), which led to many constant improvements for the library itself.
For us, even in the current state, hooks make sense in a lot of cases already, and where it still rusty, we are part of discussions on his repo and where people offer different solutions, strategies and best practices.
I would love if @trojanowski will be part of the discussions inside Apollo and their talks with the React team about the implementation. Might help make those as open as those happening on @trojanowski 's library.
@trojanowski your work is inspiring - thank you ❤️
hey @n1ru4l, thanks for the suggestion :)
I was very sad when apollo killed the HOC api, which was, in my opinion, better syntax for 90% of the use cases compared to the renderprops.
So I tried to go as vanilla as possible for now, to refactor as soon as a new API is live, which is not long in the future anymore :)
@seeden you can always have react-apollo and react-apollo-hooks run side by side and use whatever you need from both :)
I replaced react-apollo with react-apollo-hooks and everything works great in my project except subscriptions.
I definitely appreciate the effort put into it and I'm convinced it's necessary, but I'd rather wait on official design specs for the hooks API before depending on a fork in production. I've spent a bit too much time refactoring data containers the past year 😄
@seeden you can always have react-apollo and react-apollo-hooks run side by side and use whatever you need from both :)
Pretty sure that's not a good idea.
Why not? I am doing it like that in two production apps. Nothing wrong about it. On the contrary, it's great for gradual adoption without big rewrites.
From the readme of react-apollo-hooks:
Both packages can be used together, if you want to try out using hooks and retain Query, Mutation, Subscription, etc. HOCs from react-apollo without having to rewrite existing components throughout your app.
@breytex the HOC api wasn't killed 🙂 The Renderprops are just favored in the docs, if I'm not mistaken.
Maybe let’s wait for updates from the Apollo team, if there is nothing new to say. We’re all eager to use hooks and I’m sure it’s a priority for the Apollo team. Let’s not turn this issue into a Chatroom. @hwillson could you maybe lock this thread for now.
@Urigo thank you for your kind words. I'd also like to thank @umidbekkarimov and other react-apollo-hooks contributors.
I'd be glad to help with the implementation of official hooks in react-apollo.
@seeden you can always have react-apollo and react-apollo-hooks run side by side and use whatever you need from both :)
Pretty sure that's not a good idea.
Why not? I am doing it like that in two production apps. Nothing wrong about it. On the contrary, it's great for gradual adoption without big rewrites.
@FredyC is right. It's possible to use react-apollo and react-apollo-hooks together (with the exception of server-side rendering, but I'm going to fix it).
I too am looking forward to using hooks with react-apollo (especially for mutations), but I'm not sure if they solve all use cases...there may still be a use case for render props. For our app using react-apollo, I wrote a component called QueryLoader that uses react-apollo's Query component to run the query and then checks for loading and error states so we don't have to repeat that boilerplate code in each of our components (of course, if there's some need for different loading or error handling behavior in a particular component, we still have the option of using the Query component directly). So the default behavior (thanks to QueryLoader) is to render <LoadingOverlay> for the loading state and <ErrorMessage> in the case of an error.
The problem I ran into when I tried to refactor this into a hook is that hooks seem best-suited for reusing _logic_, not _rendering_ things. So I'm not sure if something like a useQueryLoader() hook would make sense (then again, I only did a brief investigation so maybe I missed something). I found a reddit thread talking about using hooks to return components:
https://www.reddit.com/r/reactjs/comments/9yq1l8/how_do_you_feel_about_a_hook_returning_components/
Dan Abramov chimed in to say:
You can return elements directly. But don't return components.
...but for a case like our query-loader behavior, returning elements directly gets weird...consider this attempt:
export function useQueryLoader(query, options) {
let result = {}
let loadingPromise
try {
result = useQuery(query, options)
} catch (promise) {
loadingPromise = promise
}
return {
...result,
renderContent: callback => {
const children = callback(result)
return (
<QueryLoader {...result} loadingPromise={loadingPromise}>
{children}
</QueryLoader>
)
}
}
}
export const QueryLoader = (props) => {
return (
<Suspense fallback={<div>Loading...</div>}>
<QueryLoaderInner {...props} />
</Suspense>
)
}
const QueryLoaderInner = ({ loadingPromise, error, children }) => {
if (loadingPromise) {
// use Suspense to wait until the data is available
throw loadingPromise
}
return error ? <ErrorMessage errorObject={error} /> : children()
}
I realized I had just invented a less elegant version of render props, as is clear when you see the usage:
const MyComponent = () => {
const { data, renderContent } = useQueryLoader(QUERY, { variables })
// you'd better not try to access `data` here, because `MyComponent` renders twice—
// during the loading state and again after data has loaded!
return renderContent(() => (
<div>
<h2>{data.myData.title}</h2>
</div>
))
}
Maybe having to write the <Suspense fallback={...} snippet and also if (error) in every component that consumes data is a small enough amount of code that most people don't care about writing it every time even if the behavior is the same in 90% of the cases. But personally I think it would be nice to reuse that code somehow and I'm not sure that hooks provide a good way to do it... Of course, an app-specific query-loading component (with a render prop API) could still use a useQuery() hook internally, and maybe that would be the way to go.
Hi @mbrowne
I had a similar setup before hooks: a component that encapsulated showing a loading spinner and errors when doing graph queries.
This is what I did when I refactored my code to hooks (using react-apollo-hooks):
const { data, error, loading } = useQuery(movieQueries.GET_PLAYING_NOW, {
suspend: false,
})
return (
<QueryResponseWrapper error={error} loading={loading}>
<div>other components here...</div>
</QueryResponseWrapper>
)
And now that QueryResponseWrapper component is the one that handles the loading bar and errors.
@juank11memphis Thanks for the suggestion. One question: how do you access nested properties of data? It seems to me that this would cause undefined property errors unless you only need access to top-level properties of data, which is why I figured I'd need a function that returns the child components to render rather than rendering children (the "other components here" div in your example) directly.
Although @trojanowski has done some amazing work with the react-apollo-hooks. It would be really good to have the useMutation hooks to return the data, loading, and error.
Perhaps something like:
// Query
const { data, loading, error } = useQuery(GET_TODOS)
// with options
const { data, loading, error } = useQuery(GET_TODOS, { ...options })
// Mutation
const [addTodo, { data, loading, error }] = useMutation(ADD_TODO)
// with options
const [addTodo, { data, loading, error }] = useMutation(ADD_TODO, { ...options })
@eddiemoore The useMutation is a tiny wrapper around client.mutate meaning it returns a Promise. You can handle loading state by waiting for a promise (it's easy to make a custom hook for that) and you can catch errors just as easily. I wouldn't complicate API more than necessary.
Hi @mbrowne
This is a real life example of what I did:
This is how I use the QueryResponseWrapper component:
const PlayingNowMovies = ({ classes }) => {
const { data, error, loading } = useQuery(movieQueries.GET_PLAYING_NOW, {
suspend: false,
})
return (
<QueryResponseWrapper error={error} loading={loading}>
<div className={classes.blackBox}>
<Text as="h3" align="center" color="white">
Playing Now
</Text>
<Divider className={classes.centeredDivider} />
<div className={classes.carouselContainer} {...rest}>
<MoviesCarousel movies={data.movies || []} />
</div>
</div>
</QueryResponseWrapper>
)
}
And this is the actual QueryResponseWrapper component:
import React from 'react'
import PropTypes from 'prop-types'
import LoadingSpinner from '../loading-spinner/LoadingSpinner'
import ErrorMessage from '../messages/ErrorMessage'
const QueryResponseWrapper = ({ children, loading, error }) => {
if (loading) {
return <LoadingSpinner />
}
if (error) {
return (
<ErrorMessage error={error} message="An unexpected error has occurred" />
)
}
return children
}
QueryResponseWrapper.defaultProps = {
loading: false,
error: null,
}
QueryResponseWrapper.propTypes = {
children: PropTypes.node.isRequired,
loading: PropTypes.bool,
error: PropTypes.object,
}
export default React.memo(QueryResponseWrapper)
As you can see QueryResponseWrapper has nothing to do with data, it just renders children if loading is false and there are no errors.
I hope this helped :)
Maybe let’s wait for updates from the Apollo team, if there is nothing new to say. We’re all eager to use hooks and I’m sure it’s a priority for the Apollo team. Let’s not turn this issue into a Chatroom. @hwillson could you maybe lock this thread for now.
Yes please lock this thread and let's wait for the Apollo team to release their version before talking about "potential" issues.
@hwillson Your last post was exactly one month ago, and since React Hooks were released as part of React v16.8 10 days ago, I'd like to ask: do you have any updates on this?
(No pressure, only wanted to know so I can plan our migration process).
@buzinas Why would you put the effort into migrating everything to use the new APIs? HOCs do still work and I am pretty sure the FaaC API's will still work once the Apollo Team starts working on Hook/Suspense implementations.
@n1ru4l I think I could have worded better. We just started using Apollo a few weeks ago, and we're still not running it in production, so for us it would be worth it to rewrite the parts we've done, because it won't be so much effort, and we'll be able to use hooks for everything new as well.
Hi all - here's where we're at:
--
The Good
The Bad
We're still a bit concerned that working on Hooks support in React Apollo might be jumping the gun, until Suspense for data fetching is released. The final version of this will impact whatever decisions we make to adopt Hooks, and it's too early to tell how much it will impact them (but our guess is it will impact them significantly). We might be better off waiting until Suspense for data is released, as the last thing we want to do is publish a Hooks version of React Apollo that everyone starts using, only to have to change the public API again when Suspense for data hits. In the meantime, people who want Hooks support now can use react-apollo-hooks.
The Ugly
React Apollo supports HOC's via graphql, render props via Apollo Components, and soon it will support Hooks. Is this too much? Yes, there are pros/cons to using each approach, but there is a massive overlap in terms of what you can accomplish with each approach. We realize this is an issue (or maybe advantage?) across the entire React ecosystem, but one of the main things we try to do at Apollo is help give developers clear guidance on what we think is the best approach for working with GraphQL. Adding a new option (without removing an older one) might add to the confusion developers are already faced with, when it comes to picking and choosing a GraphQL data loading approach. So … if we add Hooks, should we deprecate another approach? Definitely a painful question to answer - each approach has its pros/cons, but supporting each one impacts library bundle size, maintenance overhead, documentation and knowledge sharing / training.
--
Given the above, here's our current plan:
First get Apollo Client / React Apollo 2.5.0 launched. We won't be diving further into Hooks work until 2.5 is out (which is mostly dependent on docs work at this point, so hopefully it will be out next week).
Once that's done, review react-apollo-hooks more extensively, planning out how it could be best integrated into react-apollo.
At the same time, reach out to the React team to discuss all things Suspense for data, to get a better understanding of what the impact will be to a project like react-apollo (to see if we can glean how much of the Hooks work would have to change).
Decide if we want to move forward with Hooks support, or wait for Suspense+data.
If we're going ahead with Hooks support, then we'll post a design doc outlining the results of step 2 above, and if anyone in the community wants to work on it, then awesome! Otherwise we'll tackle it.
or
react-apollo-hooks featured more prominently in the React Apollo docs, perhaps with a Hooks section and examples.Regarding "The Ugly", that seems like a very surmountable problem, so hopefully the approach for that could be decided on already by the time Suspense is fully released. Obviously which APIs to encourage and support out of the box will need to be carefully discussed within the internal Apollo team, but (and I'm not trying to start a discussion on the details here) there are good options to provide core APIs in the main package (say, hooks and ApolloConsumer) that can be easily used to support the other APIs via separate packages. I'm sure the community would be happy to play whatever role it can here, even if that's just completing a survey and/or providing feedback on alpha/beta releases as usual.
I think dropping HOC support is totally unproblematic. Our app still uses HOCs, but because some features related to error handling are not supported with HOCs (#604) I reimplemented HOCs with render Props a long time ago, just release such a tiny implementation as a separate package. Hooks will definitely replace nearly all use cases of HOCs, recompose has declared itself unneeded with hooks on the rise, so I wouldn't worry too much about that.
My two cents to "The Ugly". It's definitely premature at this point, but it's good to discuss it for sure. I think that after hooks implementation is stabilized within the react-apollo V2, there could be a major V3 release that would drop the HOC. Projects using HOC would stay on V2 until they fully migrate and then they can switch to V3. This comes with the obvious downside that any bug fixes should be merged to V2 & V3 for the time being. But it feels like the most reasonable path.
Edit:
Not sure if there are people who would dislike Hooks and prefer HOC forever. I guess there is always an option to fork V2 when it stops being maintained (we are talking year or more here) and take care of it on your own. It's the cost for staying too much in the past 👯
To state the (potentially) obvious: it would probably be a bad thing if the same release that adds hooks also removed HOCs and/or render props, because that would force people to do a big bang migration rather than a gradual one. The React team have always done an awesome job of introducing new stuff and deprecating/removing old stuff over a couple of releases, so that usually if your project runs without any warnings, it's safe to upgrade even a major version.
When it comes to Apollo, I personally am a HOC lover, and only an occasional user of render props, for reasons that aren't relevant to this thread. There's been a few mentions here of "each approach has its pros and cons", but having watched Sophie, Dan and Ryan's talks on hooks, it kind of looks to me like hooks are superior in every way to both of the other approaches.
So, maybe it's just because they're new and shiny, and I haven't gone deep enough yet to see the cons of hooks, but personally I would be totally OK with Apollo (eventually) removing support for both HOCs and render props in favour of only supporting hooks out of the box. That would definitely be a win in terms of bundle size, and simplifying code, documentation, community advice to centre around one approach. If HOC and render prop components could be supported through separate packages (e.g. react-apollo-hocs, react-apollo-components), that feels like an 'everybody-wins' result.
I would really love to see some of those people who downvote dropping HOC to actually express some reasoning why they need it: @brainkim @rj254 @volkanunsal @remi2j. Is it purely an annoyance of refactoring old code or is there something else?
it kind of looks to me like hooks are superior in every way to both of the other approaches.
There is one weak spot - conditional rendering. I know, there is a skip option, but in some cases it's not optimal. There might be some variables which you cannot fulfill at the time (you get them from eg. another query) and you would need to add weird conditions there. The Query prop kinda wins this one.
If HOC and render prop components could be supported through separate packages (e.g.
react-apollo-hocs,react-apollo-components), that feels like an 'everybody-wins' result.
It's a brave idea, but I don't think anybody would have a will power to maintain it, except perhaps those people who would need it and that's kinda the "fork idea" I was talking about before.
Since you asked @FredyC. Deprecating HOC is not just an "annoyance." It's a considerable undertaking for those of us who are fully invested in the HOC pattern. It requires rewriting thousands of lines of code across several projects, and it directly affects the bottomline of our business.
Secondly, conditional rendering is an issue, like you mentioned above. We have several places where this pattern is used.
Third, there are scenarios where using HOC is more advantageous than using hooks. It is a common pattern to drill into a query result and store only a small part of it in a prop above your render component. You cannot do this if you're using hooks.
@volkanunsal Thank you for your bravery :)
Deprecating HOC is not just an "annoyance." It's a considerable undertaking for those of us who are fully invested in the HOC pattern. It requires rewriting thousands of lines of code across several projects, and it directly affects the bottomline of our business.
Alright, fair enough. Do you have for example some periodic library updates? I mean unless something really big happens in GraphQL world, the current version of react-apollo is just fine for you, right? Now imagine if there would be V2 which would still keep HOC + Render prop and add Hooks so you can choose any of that. For others, there would V3 which would drop HOC. Would you be forced to use V3 ever? I don't think so :)
It is a common pattern to drill into a query result and store only a small part of it in a prop above your render component. You cannot do this if you're using hooks.
That's far from the truth. On the contrary, Hooks allows much better composition than HOC could ever dream of. Hooks are not forcing you to punch everything into one big component. It's only a matter of open mind and a bit of a fantasy to see some really amazing patterns.
That's far from the truth. On the contrary, Hooks allows much better composition than HOC could ever dream of.
No, it's not. How would you cache intermediate artefacts of a query result using hooks? This kind of cache is absolutely essential if you're doing complex transformations with your query results. Also –– and I just remembered this –– since the data object turns into an empty object when Apollo is loading new data (at least that is what react-apollo-hooks does with the data object), you need to store the results somewhere as Apollo fetches new data if you don't want to put your component into "Loading" state or have it completely broken. That is why I haven't been able to use Apollo hooks anywhere except in trivial components that don't seem too badly affected by the flakiness that occurs because of this.
I'd be happy to gradually adopt hooks into my codebase, but I wouldn't want to give up an old technique that is still more complete than a new one, no matter how amazing you say it is. From the looks of it, it doesn't do everything HOC can do right now.
@FredyC I rewrote one of my components using Hooks and HOC to think more about this. I still think the second one is more elegant, but I admit using the state hook can address the intermediate data issue I raised above.
If the data object wasn't emptied out during data fetch, I might be able to get rid of the NonEmptyObject checks, too. Then, the hook approach would be just as elegant as the HOC approach.
Hooks
const DataList = ((props: { companyId: ID, filters: string[] }) => {
const opts = {
suspend: false,
variables: {
filters: props.filters,
companyId: props.companyId,
},
};
const { data, loading } = useQuery(FavoritesQueryDef, opts);
const [newData, setData] = useState(data);
if (data !== newData && NonEmptyObject.is(data)) {
setData(data);
}
const hasData = NonEmptyObject.is(newData);
// ...
return (
<React.Fragment>
{items}
<Loading show={!hasData && loading} />
</React.Fragment>
)
}
HOC
const DataList = graphql(FavoritesQueryDef)(props => {
const { data, loading } = props
// ...
return (
<React.Fragment>
{items}
<Loading show={loading} />
</React.Fragment>
)
})
@volkanunsal
If the
dataobject wasn't emptied out during data fetch, I might be able to get rid of theNonEmptyObjectchecks, too. Then, the hook approach would be just as elegant as the HOC approach.
I have noticed this as well. There are same underlying concepts inside the HOC and Hooks, so unless HOC is doing some weird voodoo magic, I am fairly certain it's just a bug in Hooks version, but had no capacity to pay attention to it.
Also –– and I just remembered this –– since the
dataobject turns into an empty object when Apollo is loading new data (at least that is whatreact-apollo-hooksdoes with the data object), you need to store the results somewhere
You mean when variables change and query is re-run? Haven't noticed that really, but it's definitely super easy to abstract such behavior into custom hook if you need a previous result available.
In the HOC you left out types and variables for a brevity? :) Especially those variables are very awkward with HOC since you cannot just access props within a closure (yay Hooks). And types to be defined with HOC instead of with component is also strange. Let's have a look at a real example, shall we? :)
// had to separate it, as it would be too unreadable mixed with the component
const decorate = graphql<{ companyId: ID, filters: string[] }>(
FavoritesQueryDef, {
options: props => ({
variables: {
filters: props.filters,
companyId: props.companyId,
}
})
}
)
const DataList = decorate((props => {
const { data, loading } = props
// ...
return (
<React.Fragment>
{items}
<Loading show={loading} />
</React.Fragment>
)
})
How would you cache intermediate artefacts of a query result using hooks? This kind of cache is absolutely essential if you're doing complex transformations with your query results.
Not sure what you mean by that, like a memoization? What about useMemo? You could have seriously dope custom hooks that would accept selectors to make that happen. Or any other kind of cache you would like. While with HOC it's rather hard to make a custom one, Hooks are just functions so you can use whatever you like in them.
I'd be happy to gradually adopt hooks into my codebase, but I wouldn't want to give up an old technique that is still more complete than a new one, no matter how amazing you say it is. From the looks of it, it doesn't do everything HOC can do right now.
Sure, Hooks are still fresh out of the oven and more patterns will certainly emerge over time. And fair about of bug will be squashed as well :) HOC had more time to grow, so it feels much more polished for sure.
Not sure what you mean by that, like a memoization? What about
useMemo?
useMemo is something like a pure component, right? It's not exactly what I had in mind. I meant saving transformed artefacts. I think state hooks will do a good enough job for that.
I rewrote useQuery with a custom hook to eliminate the NonEmptyObject checks in the render component. We can apply the same technique for transformed artifacts, as well. It was easier than I thought it would be. Still not as elegant as HOC, but it's definitely in the same ballpark with HOC now.
function useCachedQuery(query, opts) {
const { data, error, loading } = useQuery(query, opts);
const [newData, setData] = useState(data);
if (data !== newData && NonEmptyObject.is(data)) {
setData(data);
}
return { data: newData, error, loading };
}
const DataList = ((props: { companyId: ID, filters: string[] }) => {
const opts = {
suspend: false,
variables: props,
};
const { data, loading } = useCachedQuery(FavoritesQueryDef, opts);
const hasData = NonEmptyObject.is(newData);
// ...
return (
<React.Fragment>
{items}
<Loading show={!hasData && loading} />
</React.Fragment>
)
}
I can see that hooks aren't much worse than HOCs in terms of storing intermediate query artifacts, like I thought. Thanks for the nice discussion @FredyC.
I hope that other people can see what you have just learned 👍 I believe this is kinda a root of the problem. People are used in some ways and they are content about them because there was nothing better at that time. Why would they bother with some Hooks, right? There is an obvious barrier which is hard to beat for sure, but it can pay back (with dividends) 💰
useMemois something like a pure component, right? It's not exactly what I had in mind. I meant saving transformed artefacts. I think state hooks will do a good enough job for that.
No, that's React.memo you are talking about. The useMemo is really for memoization purposes.
Btw, tiny nit :) No need to repeat that hasData check in every component, right? You could even "merge" it with loading and hide that away completely. With HOC something like that is much harder to do as you need to add another HOC in middle to modify props.
const { data, loading, hasData } = useCachedQuery(FavoritesQueryDef, opts);
Best part of about programming is the amount of code you delete!
As with any new, shiny technology, I think it's important to be wary of too much "silver bullet" thinking. But having said that, I think hooks are a particularly good fit for graphql/Apollo. I doubt that direct usage will be superior 100% of the time, but hooks could still be the underlying implementation even in those cases. @FredyC suggested that extracting HOCs and render props into a separate package might lead to a situation where they're not sufficiently maintained, but if you think about how hooks could be used to implement those same APIs, there's really not that much to maintain...for example, the Query component could be implemented as simply as:
import { useQuery } from 'react-apollo-hooks'
export default function Query({ query, children, ...otherProps }) {
return children(useQuery(query, { ...otherProps, suspend: false }))
}
And you can even still use it from class components! ...if you need to maintain components you don't intend to migrate to function components (assuming you intend to migrate at all).
To be a bit more clear about where direct hook usage might not be the best, I'm still not convinced that the QueryLoader use case I mentioned above is improved in any way by the hook implementation suggested by @juank11memphis (much as I appreciate his feedback, the render prop solution still seems better to me from the usage perspective). That use case might always be handled in app code (as it is now in my app) rather than being supported by react-apollo directly, so I don't think this thread is the right place to discuss the tradeoffs (maybe we can discuss it elsewhere). But just to illustrate my above point, that use case too could be implemented by hooks behind the scenes and the external API could still use a render prop as my current QueryLoader component is doing:
function Demo() {
return (
<QueryLoader query={MY_QUERY} variables={...}>
{({ data }) => (
<div>
{data.foo}
</div>
)}
</QueryLoader>
)
}
I'm actually less concerned about the conditional rendering use case. I might be missing something, but I think that could be easily handled by an alternative mode for useQuery() (perhaps activated by a simple boolean parameter) that returns a function that executes the query rather than executing the query immediately—similar to how you can call react-apollo-hooks's useMutation() to return a mutation function that you call later. So something like:
const runQuery = useQuery(MY_QUERY, { curry: true })
...
// later, possibly inside some condition...
<Suspense fallback={<div>Loading...</div>}>
{/* Passing a function to React.createElement() might not be the best idea here;
just keeping the example concise */}
{React.createElement(() => {
const { data, error } = runQuery({ variables: ... })
...
})}
</Suspense>
As with everyone in this thread I've been very interested to know what the future of React Apollo holds regarding hooks and also Suspense for the future.
I agree the remarks of The Ugly and would love to see Apollo come up with a go-to solution for the broader audience. It would be great that the usage of React-Apollo leads you to the most acceptable and clear solution to add data fetching in your app. I find this especially important for data fetching because it is the main portion of state of any medium to large size application so getting this right is a big deal.
With that said, and looking at how Hooks are the new solution forward I think it's more than logical to focus this solution into that direction, mainly because Hooks seem to be the way the React team wants to move forward as well.
I personally see this like how Render Props came into the eco-system, the only big different with Hooks and Render Props are that Hooks are an official feature React itself, while Render Props was a powerful pattern that emerged from the community.
Even though Hooks are still young they do seem to solve the majority of problems and it also seems that the React team is pushing Hooks to be the solution to the majority of state management. I also notice that for the majority of problems you can solve with Render Props or HoC's there is a Hooks variant you can write, and most importantly if you can implement it with Hooks you can wrap it in the previous patterns (being Render Prop or HoC).
I agree with @camjackson that the migration is a point to be discussed and taken carefully but what I would love to see is for React Apollo to be pushing the standardized solution which I believe will be Hooks but then also offer me quick bindings to convert Hook implementations into HoC's or Render Props. @mbrowne shows some relevant use-cases for this which. I think here React Apollo could come with additional packages or documentation support to help users implement such use-cases (I'm thinking something like react-apollo-hoc or react-apollo-renderprop which could be tiny packages that wrap the official Hook implementation into a consumable HoC or RenderProp component.
We currently have a code base that is full of Higher order components and the re-compose pattern. We are on the fence to move to Hooks because a lot of this is decided by having it baked into the data fetching layer which at this point is to either wrap it ourselves or resort to react-apollo-hooks, but this has the risk that we'll have to re-do our work once React-Apollo ships with its own official hooks and we'd like to avoid doing a double refactor.
I do like to share some insights on how we handled above the good / the ugly in i18next.
We took the chance to make a full rewrite of our react integration for the i18next i18n lib. For us that had big impact in reducing module size and clean up a lot of stuff.
Why had we similar "ugly" problem? i18next supports adding "backend" plugins to load translation files from the backend server -> so our hooks might be in state "loading/notReady" or "loaded/ready" but we decided to use Suspense because Suspense itself is defined and works very well -> throw a promise which gets catched higher up the JSX tree and suspense rendering until that suspense gets resolved: https://github.com/i18next/react-i18next/blob/master/src/useTranslation.js#L76
Why do we feel save to do so:
See also the same discussion we had about using suspense and breaking the rules of hooks:
-> https://github.com/i18next/react-i18next/issues/735
-> React issue about throwing a Suspense from Hook: https://github.com/facebook/react/issues/14848
the hook enabled us to provide other usage options like HOC and render prop by reusing the hook:
This is just what we did and the case for apollo might be a lot different. So just take this as is.
Which version is expected to support the react hook ?
@sessionboy Please, read https://github.com/apollographql/react-apollo/issues/2539#issuecomment-464025309
@FredyC #2539. It's here. But it seems to be still under discussion.
@sessionboy But it kinda answers your question, right? Since no one knows, then no one can give you a better answer.
I would like to share with everyone my experience with Apollo-GraphQL and React-hooks while building a WhatsApp-Clone. So far when I wanted to run GraphQL operations I had to implement dedicated React.Elements within the virtual DOM tree, which from an architectural perspective never really made sense to me. The virtual DOM's purpose is to build the view by binding data-models into a template, and so by adding data-fetching into the mix you basically abuse it. In addition, having to use more than one GraphQL-operation for a React.Component can cause these extra indentations which basically brings us back to the issue with ES5 where we had a callback hell; this is why they invented Promises and later on async/await.
Because of that, I was fairly excited when I first noticed @trojanowski's react-apollo-hooks package. Not only it solves all the issues above, but it also supports React.Suspense for data-fetching. This way you can control which nodes up the virtual DOM tree are gonna remain visible while waiting for the data to fetch, regardless of the implementation of the fetching React.Component. The clear advantage of using React.Suspense over not using it, was that I didn't have to handle multiple loading flags; It saved a lot of time and potential errors. Sure there are some limitations to that, such as concurrent data-fetching (something like Promise.all), but still, it all made a lot of sense and it just worked. I would love to see Apollo heading that direction.
import gql from 'graphql-tag';
import { useQuery } from 'react-apollo-hooks';
const GET_DOGS = gql`
{
dogs {
id
breed
}
}
`;
const GET_CATS = gql`
{
cats {
id
breed
}
}
`;
const Animals = () => {
// No extra indentions, easy to read and maintain
const { data: { dogs } } = useQuery(GET_DOGS);
const { data: { cats } } = useQuery(GET_CATS);
// return ...
};
Good point about the virtual DOM—that's one of the benefits the React team cites for hooks as well. The principle I have gathered from my understanding so far is, "hooks for shared logic, components for rendering". Data-fetching certainly falls in the "shared logic" category. (I'll have to think more about my QueryLoader component and whether or not it actually justifies adding another element to the virtual DOM. It does do rendering—the loader component, error message, or content as the case may be.)
Side-note about concurrent data-fetching and suspense: you've probably aware of this already, but I think ConcurrentMode could help with this: https://reactjs.org/blog/2018/11/27/react-16-roadmap.html#react-16x-q2-2019-the-one-with-concurrent-mode
I would suggest to pay attention to the format of the returned tuple apollo hooks. The object form is useful when the number and names of data elements are not obvious or unknown. For example, this is convenient for the response itself, which depends on the request.
The advantages of an object-based tuple might be convenient at the initial stage of development when it is not obvious what data is required and can always be expanded.
The disadvantage is the necessary for additional actions to match the names of fields and variables:
const { data: dogs, error: dogsError, loading: dogsLoading } = useQuery(GET_DOGS);
const { data: cats, error: catsError, loading: catsLoading } = useQuery(GET_CATS);
The disadvantage of an array-based tuples is having of rigidly binding parameters to their position. Deleting an item will require the loss of an entire position and problems with using api. However, today api has been used for quite some time and can be considered fairly settled.
The main advantage of array-based tuples is the ability for the user to set variable names without additional actions:
const [ dogs, dogsError, dogsLoading ] = useQuery(GET_DOGS);
const [ cats, catsError, catsLoading ] = useQuery(GET_CATS);
@oklas Why not just like that? :)
const dogs = useQuery(GET_DOGS);
const cats = useQuery(GET_CATS);
dogs.data !== cats.data
Sure, it's slightly more verbose, but considering this is rare to call several queries within a single component, I wouldn't be complicating API much because of that. Besides, you can always merge those queries on document level and get that in a single package from the server.
@FredyC This is cool. But, dogs.data && dogs.data.dogs && dogs.data.dogs.map(...
Another example.
If I do like that:
const { data: { dogs } , error, loading } = useQuery(GET_DOGS);
prettier will transform that code to:
const {
data: { dogs },
error,
loading,
} = useQuery(GET_DOGS);
Or with array syntax:
const [{ dogs } , error, loading] = useQuery(GET_DOGS);
The nice thing about hooks is that you can always compose it with a custom hook to the syntax you prefer :)
@sandiiarov Really, why don't you just merge that query into a one? You would save yourself a lot of trouble. And if you want to stay DRY, just use fragments for common field selections.
query {
dogs { ... }
cats { ... }
mices { ... }
}
Otherwise, what @rovansteen said, just make yourself a custom hook that would give you results in the tuple form. No harm in that.
I can give you the same advice with a custom hook or custom HOC or even with a custom render props component. Just make yourself a custom hook 😄. Is it helpful? Do you wrap useState into a custom hook because you don't like syntax? I don't think so. Why should I do that with react-apollo? I would prefer to use the standard syntax even if I don't like it.
I am just trying to show the pros of that solution.
Hooks are way easier to compose than render props and HOCs. I agree with you and I personally prefer the tuple syntax. I’m just pointing out that whatever syntax will be implemented you can easily make a custom hook to fit your preference or use case because they compose so well.
@sandiiarov There is a major problem with a tuple in this case. It's rather opinionated. Does loading go as the second or third argument? What about refetch, fetchMore and several other helpers?
const [ dogs, dogsError, dogsLoading, { refetch: refetchDogs } ] = useQuery(GET_DOGS); 😮
GraphQL gives you the nice ability to merge queries together. Feels like you want to shoot yourself in the foot deliberately not wanting to use that 😄
Besides, all that loading & error handling shenanigans are pretty much temporary. With Suspense we won't need loading in most cases. And for errors, they will be most likely be rethrown and caught with error boundary. You would design API here that would become obsolete eventually.
👆🏻 This exactly.
I always create my own components (or in the future hooks) on top of Apollo because I want them to be opinionated for my project and use cases. E.g. I always throw errors because I never want to handle them in my query components. But that doesn’t make it a good baseline API for everyone.
I like the tuple API for its simplicity but I agree with @FredyC that the order can be very confusing. So I think it makes most sense to stick with the current objects until Suspense simplifies everything massively.
@FredyC this is a good point. I agree with you.
Reading this threads made me seriously rethink about current state of simplicity in design.
As a developer, i'm also the customer of library author like yours.
What a customer want ? Simplicity, right ?
Just make a generic useGraphql is enough to me, with the whole parameters taken from Official Fetch API.
So, i can just use useGraphql, put my graphql query there (it could be anything, query, mutation, subscription, or whatever, because it's just fetch after all.
So, as a customer, what i expect from the library is the library could do some "smart" things like caching, invalidation, reconnect,... for developers.
In my work, even i use apollo, my API design is just
const [data, loading, errors] = useGraphql({uri, headers, query})
That query could be a graphql query, mutation, subscription, it doesn't matter to me, as the consumer of a library.
So, just minimize API surface, remove all the redundant APIs like Query, Mutation, Subscription,... They're identical, indifferent to me under consumer point of view. No more graphql, What's graphql ? It's just a string i put in my POST request after all.
Last words: Keep API surface minimal, so that instead of the big documentation page, shape it so that it could fit a small github README instead.
@revskill10 I would say that's kinda blind-folded view on the matter. All three operations ARE different and there are different needs for each of them. Doesn't matter if there is just a fetch underneath (or websocket in case of subscriptions). This is where GraphQL shines and gives you much better DX given some thought.
Apollo has always been focused on better DX. If you want simplicity go with eg. graphql-request from Prisma. There is a bunch of others, just pick your favorite 😉
// or go like this and you don't even need `react-apollo` at all
const client = useApolloClient()
client.query(...)
client.mutate(...)
client.subscribe(...)
I get that things feel difficult for you. At least that's the impression I got from your comment. It's understandable, every one of us has been there. But that's a beauty of our profession, to be evolving and learning. Going with simple is no fun imo :)
What a customer want ? Simplicity, right ?
You know that every customer is different, right? Some people actually enjoy complicated stuff.
So, as a customer, what i expect from the library is the library could do some "smart" things like caching, invalidation, reconnect,... for developers.
Without separating type of operation, library cannot be that smart. Subscriptions usually have a different data shape and need some special treatment. What would "reconnect" mean for queries or mutations?
Just a few bullet points...
Interestingly enough I am currently trying to rewrite a useMutation from the hooks package because it's really not that simple if you want to write some serious app that actually cares about UX.
@FredyC If you look again at my API usage
const [data, loading, errors] = useGraphql({uri, headers, query})
This API has some interesting parts:
uri params: So that i could use graphql with many endpoints.client for consumer to create BEFORE writing business code.It's what make ReactJS great since day one: Keep API surface minimal.
Do you care if that query is a query, mutation or subscription ? I guess no. It's the implementation detail that takes care for it.
I'm currently use above API for query and subscription, not mutation yet. mutation is an interesting case, and actually, that data for mutation is the action that you will fire later on.
So my point is, having a minimal api surface will prevent big rewrite, keep backward compatibility whatever the underneath change is.
It's my point here. If you go fancy way with thing like graphql HOC, it'll sooner or later break its API in some days, the reason is because it's fancy, right ?
Let's keep the core minimal, and let's community enhance it with community packages.
Alright, have fun with graphql-request then. Make yourself a tiny custom hook wrapper to handle loading/error states and you are good to go. 🐌 Apollo is overkill for your needs, but I am convinced you will eventually realize the mistake of your decision.
@revskill10 actually having a super minimal API surface as you suggest it will lead to everything becoming magic behind the scenes and nothing being explicitly agreed. As @FredyC said before these operations are vastly different and have vastly different needs, there are no fetch policies for mutations or subscriptions, errors mean something different etc.
I think you have a fundamental misconception about what Apollo does. Apollo’s purpose is not to send GraphQL requests, that is super easy to do with fetch yourself, you could write a basic hook as you describe it in 20 lines of code. Apollo’s purpose is first and foremost to create a global cache and subscribe components to that state (not talking about subscriptions here, but queries subscribe to the cache so you just update the cache wherever you want and your components will rerender, queries in Apollo are not like network requests but like redux selectors) and then Apollo also does error handling and customizing the network layer like refetching.
So what are you Talking about? The client is the very core of what Apollo is, you cannot simply hide it. How could one merge queries and mutations, the ones are like selectors in redux the other like action dispatchers and you define the accompanying reducer with the update function, so queries and mutations have nothing in common. Maybe Apollo needs to do a better job of explaining the whole state management part that it is about, so people don’t mistake it for a GraphQL request library.
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 ?)
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 ;)
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.
@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?
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'})
@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 :)
Any component which manage relations between more then one different objects
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.
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.
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.
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.
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].
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.
Easy expansion of the existing API with format-like method(s):
const [data, refetch] = useQuery(...).asState()
@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.
@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!
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
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();
Valid syntax for eliding members when destructuring is:
const [data, , , refetch] = useQuery();
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 ]
}
@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:
https://github.com/apollographql/react-apollo/issues/2539#issuecomment-464025309
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
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.
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 !
@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.
@hwillson please lock the thread!
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!
Hooks support is almost ready. Anyone interested can follow along in https://github.com/apollographql/react-apollo/pull/2892. We'll be merging into master very shortly, and releasing as soon as we get the docs updated. Thanks all!
Most helpful comment
Sorry for the delay all (last weeks meeting was re-scheduled to today). We've had some preliminary discussions about React Apollo's future Hooks support, and have set aside more time to work on it. Our biggest Apollo Client / React Apollo push for the next 2 weeks is finalizing https://github.com/apollographql/apollo-client/pull/4155, and everything that's impacted by that PR (e.g. Apollo Client Devtools, Link state, docs, etc.). After AC 2.5 is published, we'll have more time to devote to Hooks work. In the meantime however, we're working on hashing out an initial design, which we'll share with everyone here for feedback. Once we're all happy with the design, then we'll go from there - if anyone here wants to help work on the code, then awesome! :bow: