trojanowski / react-apollo-hooks Goto Github PK
View Code? Open in Web Editor NEWUse Apollo Client as React hooks
License: MIT License
Use Apollo Client as React hooks
License: MIT License
Hey, great library.
I'm trying to catch mutation errors but it seems none of the callbacks are called. The only way I was able to catch errors is to use errorPolicy: "ignore" and then checking the result in refetchQueries callback.
Is it an error or am I doing something wrong?
addUser = useMutation(mutation, {
update: (proxy, mutationResult) => {
/* NOT CALLED */
},
refetchQueries: (result) => {
/* CALLED ON errorPolicy: "ignore" */
},
onCompleted: (data) => {
/* NOT CALLED */
},
onError: (data) => {
/* NOT CALLED */
}
});
I am mostly curious what was the initial motivation to have suspend = true
by default already. There is a lack of information on how to actually use suspense for data fetching considering that it's planned for late 2019.
I just got burned by this badly when I forgot to set it false for one component deep down in the tree. I do have a Suspense
way up in a tree to handle React.lazy
. Suddenly when a query was rerun due to variables changing, everything got unmounted and fallback to top-level Suspense. It took me a while and some debugging to realize the culprit.
Do you have some actual examples of how to integrate it with react-apollo-hooks? Is it someone using it successfully already or is it everyone switching it off?
Let's consider a pretty simple scenario. A component that has useQuery
and renders some markup with data from that query. Now with loading
prop the decision what to show is done deeper in the tree. However, with suspension in play, it has to be done at top levels? I just cannot wrap my head around this for some reason.
Using the options cache-and-network
for fetch policy creates an infinite loop.
I could see that in this if
:
query === previousQuery.current &&
isEqual(variables, previousVariables.current) &&
isEqual(apolloContextOptions, previousApolloContextOptions.current) &&
isEqual(restOptions, previousRestOptions.current)
previousVariables.current
and previousRestOptions.current
and always undefined.
I don't know why useRef is not persisting.
Hi, I guess it's just not possible right now, but just to confirm I didn't do anything wrong:
import { ApolloProvider } from "react-apollo-hooks";
import { Rehydrated } from "aws-appsync-react";
import appsyncClient from "./appsyncClient";
<ApolloProvider client={appsyncClient}>
<Rehydrated>
<App />
</Rehydrated>
</ApolloProvider>
throws the following error:
Unhandled Rejection (TypeError): Cannot read property 'hydrated' of undefined
If I use the ApolloProvider from react-apollo, it works fine. I don't know how difficult it would be to add support for it.
Thanks.
with getDataFromTree
you could wrap the call in a try-catch
block to prevent graphql errors from crashing app, since you could still render the html markup
but since getDataFromMarkup
combines fetching data and rendering html, how should server side graphql errros be handled if you're not yet using suspense?
I updated to latest version 0.3.0 and saw below error being thrown. I thought that maybe it's something new added to React 16.8.0-alpha but no. What is this SuspenseProps imported from 'react'?
.../node_modules/react-apollo-hooks/lib/SuspenseSSR.d.ts
Type error: Module '".../node_modules/@types/react/index"' has no exported member 'SuspenseProps'. TS2305
1 | import { SuspenseProps } from 'react';
| ^
2 | export declare function unstable_SuspenseSSR({ children, fallback }: SuspenseProps): JSX.Element;
I'm using the defaultOptions
option on ApolloClient to make all queries default to a cache-and-network
policy:
defaultOptions: {
query: {
fetchPolicy: "cache-and-network"
},
watchQuery: {
fetchPolicy: "cache-and-network"
}
}
When using React Apollo directly (either via the graphql()
HOC or the <Query />
component), my queries are automatically being executed whenever my component mounts. However, using useQuery
only executes the query if the cache is empty. If I explicitly pass fetchPolicy: "cache-and-network"
as an option to useQuery
it works fine, but I'm not sure why the defaults set on the ApolloClient instance are being overridden?
I got a repro here which I made for the article (in works): https://codesandbox.io/embed/qzjzr583yq
Watch the console
Now the strangest part is that if you click on the little button to refresh same tag, it works perfectly.
If there is some mistake on my part, I do apologize, but I wasn't able to spot it.
You can also see error reported in #74 in here if you enter some tag that has no giphy associated.
When you use the "update"property in mutation options like :
const myMut = useMutation<MyMut, MyMutVars>(MYMUT, {
update: (cache, result) => {
//// result has type FetchResult<MyMut>
//// result.data has type ExecutionResult<ExecutionResultDataDefault> instead of an expected ExecutionResult<MyMut>
}
});
I think the problem comes again from the FetchResult type as defined in apollo-link. The only workarround I think is to redefine MutationOptions/MutationUpdaterFn type to use the FetchResult type you already redefine. :s
I'm trying to pass a mutation function to a function using typescript, the problem is that when I try to pass the mutation I don't know which type should I use.
const loginMutation = useMutation(loginQuery, {
variables: { email, password },
});
export const loginAction: LoginAction = async (
loginMutation, // Which type loginMutation should I assign here?
) => {
try {
const { data } = await loginMutation();
console.log(data.login.token);
} catch (error) {
console.error(error)
}
};
Been using this library for a while now and it's been perfect ๐
I just upgraded to the latest stable release of React 16.8.0 and then my console was full of errors saying:
Rendered more hooks than during the previous render. This is not currently supported and may lead to unexpected behavior.
After debugging i found it only happens when the component is wrapped in a memo()
Not sure where the issue should be posted though?
Curious if you'd be interested in adding hooks for Apollo Link State? I've struggled a bit with finding a simple/repeatable way to use it, and hooks seem like they may be exactly what the doctor ordered.
Perhaps to start with, something as simple as:
declare module "react-apollo-hooks" {
function ApolloProvider({ children, client }: any): any;
function useApolloClient(): any;
function useApolloQuery(query: any, { variables }?: any): any;
function useApolloMutation(mutation: any, baseOptions?: any): any;
}
With react-apollo, it's possible to set a key={id} prop on the Mutation component
e.g
<Mutation mutation={UPDATE_TODO} key={id}>
Apollo then updates the correct item in the cache, wondering if this is currently possible with this library?
some questions / concerns
getMarkupFromTree
code and doing ssrManager.consumeAndAwaitPromises().then(process);
seemed weird, there doesn't seem to be a base case so you're essentially calling the render function until the promises are finished? perhaps i didn't look at the code enough, or I don't fully understand the way suspense works, but my superficial reaction was that something looks off.in regards to point 1, an alternative can be to expose a ServerApolloProvider
that includes the ApolloContext
and SSRContext
. that way getDataFromTree
and renderToString
can be separate
Hello,
I'd like to use react-apollo-hooks with Storybook.
I saw your example for testing, but in the context of Storybook I can't call waitForNextTick
.
So I'm stuck in the Loading
state when trying to build my component in isolation :
import { Suspense } from 'react'
import { ApolloClient } from 'apollo-client'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { MockLink } from 'apollo-link-mock'
import { ApolloProvider } from 'react-apollo-hooks'
import { storiesOf } from '@storybook/react'
import { GET_BOITE, BoiteDetails } from './BoiteDetails'
let MOCKS = [
{
request: {
query: GET_BOITE,
variables: { where: { id: '123' } }
},
result: {
data: {
id: '123',
libelle: 'Boite 123'
}
}
}
]
let addApolloProvider = mocks => story => {
let client = new ApolloClient({
cache: new InMemoryCache(),
link: new MockLink(mocks)
})
return (
<Suspense fallback="Loading...">
<ApolloProvider client={client}>{story()}</ApolloProvider>
</Suspense>
)
}
storiesOf('Parametres/Boites', module)
.addDecorator(addApolloProvider(MOCKS))
.add("Dรฉtails d'une boite", () => <BoiteDetails id="123" />)
I'm able to query without issue, but am getting the error
Hooks can only be called inside the body of a function component.
when trying to perform a mutation.
const mutate = (variables) => {
const mut = useMutation(UPDATE, { variables });
mut();
};
return (
<Details update={mutate} {...otherProps} />
);
};
"apollo-cache-inmemory": "^1.3.12",
"apollo-client": "^2.4.8",
"apollo-link": "^1.2.6",
"apollo-link-http": "^1.5.9",
"apollo-link-mock": "^1.0.1",
"apollo-link-ws": "^1.0.12",
"apollo-utilities": "^1.0.27",
"react-apollo": "^2.3.3",
"react-apollo-hooks": "^0.2.1",
"react-dom": "^16.8.0-alpha.0",
"react-scripts": "^2.1.3-next.6a95aae9",```
Steps to reproduce:
refetch
.Hi
react-apollo redefine FetchResult and ExecutionResult type to get the correct type for the data object available in the mutation promise :
https://github.com/apollographql/react-apollo/blob/49ceaae683599e058009606eed9f933edab5886e/src/Mutation.tsx#L38
https://github.com/apollographql/react-apollo/blob/49ceaae683599e058009606eed9f933edab5886e/src/Mutation.tsx#L25
Is it possible to do the same in your hooks version ?
Anthony.
I am using next.js with Apollo, and have been writing Hooks based components. I noticed react-apollo-hooks doesn't seem to work with getDataFromTree when it does SSR and fills the cache. If I use from react-apollo it correctly sets the initial data in the apollo cache and doesn't refetch it, but useQuery only seems to fetch client side... is this expected/going to be fixed?
export function useQuery<TData = any, TVariables = OperationVariables>(
query: DocumentNode,
// QueryHookOptions
options?: QueryHookOptions<TVariables>
): ApolloQueryResult<TData> & {
fetchMore<K extends keyof TVariables>(
fetchMoreOptions: FetchMoreQueryOptions<TVariables, K> &
FetchMoreOptions<TData, TVariables>
): Promise<ApolloQueryResult<TData>>;
};
type QueryHookOptions<TVariables> = Omit<QueryOptions<TVariables>, 'query'> & {
suspend?: boolean;
};
current workaround
useQuery<A, B>(Query, { notifyOnNetworkStatusChange: true } as any);
what I expected
useQuery<A, B>(Query, { notifyOnNetworkStatusChange: true });
index.d.ts
// now
type QueryHookOptions<TVariables> = Omit<QueryOptions<TVariables>, 'query'> & {
suspend?: boolean;
};
// should be
type QueryHookOptions<TVariables> = Omit<WatchQueryOptions<TVariables>, 'query'> & {
suspend?: boolean;
};
export interface QueryOptions<TVariables = OperationVariables> extends QueryBaseOptions<TVariables> {
query: DocumentNode;
metadata?: any;
context?: any;
}
export interface ModifiableWatchQueryOptions<TVariables = OperationVariables> extends QueryBaseOptions<TVariables> {
pollInterval?: number;
notifyOnNetworkStatusChange?: boolean;
}
export interface WatchQueryOptions<TVariables = OperationVariables> extends QueryOptions<TVariables>, ModifiableWatchQueryOptions<TVariables> {
}
cause in index.js
, useQuery calls watchQuery so restOptions should have WatchQueryOptions interface too
export function useQuery(query,
{
variables,
suspend = true,
context: apolloContextOptions,
...restOptions
} = {}
) {
// ...
// ...
const watchedQuery = client.watchQuery({
query,
variables,
...restOptions,
});
// ...
}
PS
how can i skip query?
<Query {...options} skip={true}>{renderProps}</Query>
// vs
useQuery<A, B>(Query, { skip: true } as any); // not work cause apolloClient dosn't have skip option
Hello,
I'm trying to see my mutation after writing a query into the store.
Previously on a mutation update when I write something to the store with writeQuery all queries results were updated automatically on screen.
It seems that now useQuery is not called twice on rerender when no input args changed. I found a workaround something like this :
const { data, refetch, fetchMore } = useQuery(POST_FEED, { variables: variables, key: something });
And on each mutation I change the key value. But I don't think it's the solution for the problem.
When the following simple query is compiled with Typescript:
const Demo = () => {
const { data, error, loading } = useQuery(GET_DEMOS)
}
Typescript complains about error
not being found on the return type of useQuery
, error message is as follows:
Property 'error' does not exist on type 'ApolloQueryResult<any> &
Pick<ObservableQuery<any, OperationVariables>, "refetch" | "startPolling" | "stopPolling" | "updateQuery"> &
{ fetchMore<K extends string | number>(fetchMoreOptions: FetchMoreQueryOptions<...> & FetchMoreOptions<...>): Promise<...>; }'.
The definition of ApolloQueryResult
can be found in the apollo-client repo, and apparently only errors
are defined there. Changing error
to errors
in the code above does fix the problem. However, to maintain consistency with react-apollo
, it would be great to define error
.
It is necessary to include the Apollo word in function names? I think it would be much cleaner without it
useQuery
useMutation
We could have both and deprecate the old names with a message.
How can we run useQuery manually again if lets say a state attribute is updated.
Were you planning on or Would you object to somebody taking this repo and trying to contribute this back to react-apollo?
I love what you've started here, and I want to help!
Is it normal with this new hooks api to re-render a component multiple times? I just saw that useQuery
causes a component to re-render 3 times. Here is an example: https://codesandbox.io/s/j7kw103525
It's the same, example from the readme file, I have just added a console log to /views/Details.js
const Detail = ({
match: {
params: { breed, id }
}
}) => {
const { data, error } = useQuery(GET_DOG, {
variables: { breed }
});
console.log(2);
return (
<View style={styles.container}>
<Header text={breed} />
{error ? (
<Error />
) : (
<DogList...
And the output is:
2
2
2
I have tried on my own project and got the same result.
Use Jest and react-testing-library.
Hi,
Getting
TypeError: Object(...) is not a function
when const client = useApolloClient(); inside the component.
Thanks
Regarding this snippet:
import React, { Suspense } from 'react';
const MyComponent = () => {
return (
return (
<Suspense fallback={<div>Loading...</div>}>
<Dogs />
</Suspense>
)
);
}
return
statements are not valid in an expression context, and it seems like the example could be rewritten as:
const MyComponent = () => (
<Suspense fallback={<div>Loading...</div>}>
<Dogs />
</Suspense>
);
Are you going to publish a 0.2.2 with #44 ?
Using the query as
const { data, loading, networkStatus, fetchMore } = useQuery(query, {
variables,
skip,
notifyOnNetworkStatusChange: true
});
networkStatus
seems undefined when console logging so I can't do const loadingMore = networkStatus === 3
Great work, but how I can access subscribeToMore on query hook and update the cache after a mutation with this hooks?
I think the property data from useQuery result should not be optional.
When we look in the docs:
An object containing the result of your GraphQL query. Defaults to an empty object.
It defaults to an empty {} object. Event when I console.log, I never see data being undefined.
Apollo itself defines it as
ApolloCurrentResult<T> = {
data: T | {};
}
and
ApolloQueryResult<T> = {
data: T;
}
As we can see, it is either T or {} but never optional (undefined)
Thus the interface QueryHookState should not define data as optional.
Hello,
currently, only fetchMore() is added to the useQuery result. Is it possible to add more functions, like refetch(), start/stop polling... and more (https://github.com/apollographql/react-apollo/blob/e95e5fc60a7a52d7c54e1d55fa142e0137e4042f/src/Query.tsx#L49)
Thanks
Anthony.
It seems like even when I'm using network-only
that data from the cache is being returned immediately. I would have expected that the rendering phases would be:
{ data: {}, loading: true } // render 1
{ data: { foo: {โฆ} }, loading: false } // render 2
// done
Instead what happens is that if data from the cache exists, at render 1 data
will actually be populated with the cache data.
The React Apollo docs make it clear this should never happen:
network-only
: This fetch policy will never return you initial data from the cache. Instead it will always make a request using your network interface to the server. This fetch policy optimizes for data consistency with the server, but at the cost of an instant response to the user when one is available.
For now I'm using a wrapper that returns {}
when loading=true
and fetchPolicy=no-cache
or fetchPolicy=network-only
.
I have a mutation with refetchQueries whose data is directly impacted by the mutation. With the latest v0.3.0 update those queries no longer correctly pull the updated data even though the network tab shows the correct data being returned from the server. I can force other queries to execute by navigating back and forth between another page and the data successfully updates the second time.
Rolled back to v0.2.1 and everything works as expected. Will work on debugging further.
Looks like the ObservableQuery gets torn down (tornDown = true) after the last subscriber is removed but the new code still keeps it in query cache. This query will never return updated data.
When a error within the apollo-client
or the react-apollo-hooks
library occurs it does not get returned as an error. React Suspend waits for the Promise, and tries to render it in an infinite loop.
https://codesandbox.io/s/zx2n727o33
even though a Network Error
from apollo-client
is written to the console every 50ms, the error does not get forwarded as an response to useQuery
, nor does it get catched by componentDidCatch
I am trying to implement a conditional pattern which I am not sure how it should work with hooks.
I have one query asking for currentUser and only when and if it returns a userId I will execute the query.
By the rules of hooks, you must not nest hooks inside conditions or loops, so how can I achieve this?
This is a sample of my custom hook:
function useSomething() {
const [userId] = useCurrentUser();
// Here we need to skip the query
const { data, errors } = useQuery(query, { variables: {userId} });
// Do some stuff and return data
return [data, errors]
}
Can someone share a way to implement the use of useQuery within a useEffect method?
I would think there would be a way to initiate a query again based on an attribute that has been changed that an Effect is listening to.
As a basic example: 2 Dropdowns, where the first dropdown's selection needs to update the options in the second dropdown. (Country, Region)
currently returned value both of useMutation
and useQuery
calculated every time they called, I'm not really familiar with apollo logics, but I guess this can memoized?, for instance useMutation
should be similar like this:
export function useMutation(mutation, baseOptions) {
const previousOptions = useRef();
const client = useApolloClient();
const result = isEqual(previousOptions.current, baseOptions);
return useMemo(
() => {
previousOptions.current = baseOptions;
return localOptions => client.mutate({
mutation,
...baseOptions,
...localOptions,
});
},
[
mutation,
result ? previousOptions.current : baseOptions,
],
);
}
Hello,
I was curious if you knew what the behavior would be like if this package was used with server side rendering.
My understanding is that Suspense is not currently supported when server side rendering. I think it shows loading state for Suspense components. I'm not sure how the interaction will play out when used in combination with the data collector from Apollo Server.
Do you happen to have any insight on this? Thank you.
Right now errors are returned as a part of the useQuery
result. Maybe a better idea would be to throw them and recommend to use Error Boundaries instead (at least in the suspense mode).
So instead of this code:
const Hello = () => {
const { data, error } = useQuery(GET_HELLO);
if (error) return `Error! ${error.message}`;
return <p>{data.hello.world}</p>;
};
we could write:
const Hello = () => {
// we can be sure here that the error hasn't occurred
return <p>{data.hello.world}</p>;
};
and use an error boundary at the root of the react tree (or anywhere else above the component) - similar to how we use the <Suspense />
component.
First of all, great job on the package!
I'm currently writing tests for an application made with react-apollo-hooks
, but I'm running into the issue where Jest simply freezes when I try to test queries that are deeper than one level, e.g.:
query HelloQuery {
hello {
world
}
}
I've tried to make a CodeSandbox of the issue, using @trojanowski example for tests, but please mind that CodeSandbox will try to run the tests immediately and in this case freeze the page entirely, so close the browser preview before the tests run:
When useing useQuery
(with suspense false), I get a network error, but the hook state stays with "loading: true" and errors undefined. networkState is still 1.
Iโm trying to figure out a list to make react-apollo-hooks analogue to current react-apollo version.
List from @trojanowski (#40 (comment)):
My observations (open for suggestions)
PRs:
In ApolloQueryResult type property errors should be singular just an error.
export declare type ApolloQueryResult = {
data: T;
error?: GraphQLError[]; //Changed
loading: boolean;
networkStatus: NetworkStatus;
stale: boolean;
};
Trying to run the following code:
const Status: React.FunctionComponent = () => {
const { data, errors } = useQuery(STATUS_QUERY, { suspend: false, pollInterval: 2000 });
console.log(data);
console.log(errors);
if (errors && errors.length > 0) {
return <WarningIcon fontSize="large" />;
}
return null;
};
When the query fails (e.g. when server is down) it stops polling.
Unhandled error Network error: Failed to fetch Error: Network error: Failed to fetch at new ApolloError
With react-apollo's Query component I can keep polling.
Let me know if you need more info
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.