Fetching data with React#
useQuery#
React hook that uses the main design of GQty.
This hook returns the core query
object, and it will detect the usage of it, and suspend (if enabled) when the data is being fetched for the first time, and update the component when any of the requested data changes.
Features#
- Composability
- Advanced 'stale-while-revalidate' behavior, allowing you to set any value to re-trigger re-validation.
- Extra
$state
in the returned value, which is extra valuable forNon-Suspense
usage.
Suspense Example#
import { Suspense } from 'react';
import { useQuery } from '../gqty';
function Example() {
const query = useQuery({
// boolean | undefined
suspense: true,
// boolean | object | number | string | null
// If you put an object to trigger re-validation on-demand, it should be a `memoized` value from `useMemo`
staleWhileRevalidate: true,
// ((error: GQtyError) => void) | undefined
onError(error) {},
});
return <p>{query.helloWorld}</p>;
}
function Container() {
return (
<Suspense fallback="Loading...">
<Example />
</Suspense>
);
}
Non-Suspense Example#
See Non-Suspense usage for more details
import { useQuery } from '../gqty';
function Example() {
const query = useQuery({
// boolean | undefined
suspense: false,
// boolean | object | number | string | null
// If you put an object to trigger re-validation on-demand, it should be a `memoized` value from `useMemo`
staleWhileRevalidate: true,
// ((error: GQtyError) => void) | undefined
onError(error) {},
});
if (query.$state.isLoading) {
return <p>Loading...</p>;
}
return <p>{query.helloWorld}</p>;
}
prepare#
useQuery
offers a nice helper that allows for good optimizations, allowing you to do a prepass over specific parts of your expected query, before your component ends rendering the first time.
It's basically a function that is called right before the useQuery
returns, allowing your component to either Suspend immediately
or add a conditional render using the $state.isLoading
property.
The function receives as parameters:
- The
prepass
helper. - Your auto-generated
query
object.
Since it's a function called before the hook itself returned, the returned value/values are undefined
, and unfortunately TypeScript
can't know that.
Suspense example#
import { useQuery } from '../gqty';
function ExampleSuspense() {
const query = useQuery({
prepare({ prepass, query }) {
prepass(query.users, 'id', 'name', 'dogs.name');
},
suspense: true,
});
return (
<>
{query.users.map(({ id, name, dogs }) => {
return (
<p key={id}>
Name: {name}
<br />
Dogs:
<br />
<ul>
{dogs.map(({ name }) => {
return <li key={name}>{name}</li>;
})}
</ul>
</p>
);
})}
</>
);
}
// ...
<Suspense fallback="Loading...">
<ExampleSuspense />
</Suspense>;
Non-Suspense example#
import { useQuery } from '../gqty';
function Example() {
const query = useQuery({
prepare({ prepass, query }) {
prepass(query.users, 'id', 'name', 'dogs.name');
},
suspense: false,
});
if (query.$state.isLoading) return <p>Loading...</p>;
return (
<>
{query.users.map(({ id, name, dogs }) => {
return (
<p key={id}>
Name: {name}
<br />
Dogs:
<br />
<ul>
{dogs.map(({ name }) => {
return <li key={name}>{name}</li>;
})}
</ul>
</p>
);
})}
</>
);
}
graphql HOC#
graphql
is a Higher-Order Component alternative to useQuery, designed specially for Suspense usage.
For this function it is expected to use the query
object exported by the core client, and it will detect the usage of it, and suspend (if enabled) when the data is being fetched for the first time, and update the component when any of the requested data changes.
Features#
- Specify
Suspense fallback
directly - Intercept the data requests faster than useQuery, allowing it to prevent an extra render.
- Basic 'stale-while-revalidate' behavior
Example#
import { Suspense } from 'react';
import { graphql, query } from '../gqty';
const Example = graphql(
function Example() {
return <p>{query.helloWorld}</p>;
},
{
// boolean | { fallback: NonNullable<ReactNode> | null } | undefined
suspense: true,
// ((error: GQtyError) => void) | undefined
onError(error) {},
// boolean | undefined
staleWhileRevalidate: true,
}
);
function Container() {
return (
<div>
<Suspense fallback="Loading...">
<Example />
</Suspense>
</div>
);
}
Difference between "useQuery" and "graphql"#
If you read both, you could see that both do similar stuff, but enable different things based on it's nature and React constraints.
The graphql HOC is targeted specially for Suspense usage, since it can intercept the data requests before the render phase and Suspend right away, and also specify the fallback directly, but since it's a HOC
, it's not as nice to use as hooks.
On the other hand, useQuery enables very nice composability, but it lacks the ability to intercept the Render Phase if not using useQuery "prepare" helper, which in practice it only means 1 extra render, which most of the time it doesn't matter.
And it's important to note that for Non-Suspense
usage we encourage to always use useQuery.
usePaginatedQuery#
Paginated focused queries, in which you specify a function and initial arguments, which is going to be automatically called on first-render or via custom fetchMore
calls.
Features#
- Suspense support
- Partial Fetch policy support (
'cache-first'
,'cache-and-network'
or'network-only'
) - Custom data merge function, with included
uniqBy
&sortBy
helpers. - If no new args are defined in your
fetchMore
call, the previous or initial args are used. - You can override the existing
fetchPolicy
in the second parameter offetchMore
, for example, to force arefetch
. - The helper functions are included in third parameter of main function.
Example#
import { usePaginatedQuery, ConnectionArgs } from '../gqty';
// ...
function Example() {
const { data, fetchMore, isLoading } = usePaginatedQuery(
(
// Auto-generated query object
query,
// You have to specify the arguments types, in this example we are re-using auto-generated types
input: ConnectionArgs,
// Core helpers, in this example we are just using `getArrayFields`
{ getArrayFields }
) => {
const {
nodes,
pageInfo: { hasNextPage, endCursor },
} = query.users({
...input,
});
return {
nodes: getArrayFields(nodes, 'name'),
hasNextPage,
endCursor,
};
},
{
// Required, only used for the first fetch
initialArgs: {
first: 10,
},
// Optional merge function
merge({ data: { existing, incoming }, uniqBy }) {
if (existing) {
return {
...incoming,
// If using 'cache-and-network', you have to use `uniqBy`
nodes: uniqBy([...existing.nodes, ...incoming.nodes], (v) => v.id),
};
}
return incoming;
},
// Default is 'cache-first'
fetchPolicy: 'cache-and-network',
// Default is `false`, it only applies for the initial fetch.
suspense: true,
}
);
return (
<div>
<ul>
{data?.nodes.map(({ id = 0, name }) => {
return <li key={id}>{name}</li>;
})}
</ul>
{isLoading && <p>Loading...</p>}
{data?.hasNextPage && data.endCursor ? (
<button
disabled={isLoading}
onClick={() => {
fetchMore({
first: 10,
after: data.endCursor,
});
}}
>
Load more
</button>
) : null}
</div>
);
}
useTransactionQuery#
Alternative to graphql
and useQuery
that works via pre-defined functions, which allows for extra features that are not available in useQuery
& graphql HOC
.
Features#
- Polling
- Conditional skipping
- Automatic call on variable change
- Lifecycle functions
onCompleted
&onError
- Suspense support
- Fetch policy support
Example#
import { useTransactionQuery } from '../gqty';
// ...
function Example() {
const { data, error, isLoading } = useTransactionQuery(
(query, args: string) => {
return query.hello({ name });
},
{
variables: 'John',
// By default is 'cache-first'
fetchPolicy: 'cache-and-network',
// Polling every 5 seconds
pollInterval: 5000,
// By default is `true`
notifyOnNetworkStatusChange: true,
onCompleted(data) {},
onError(error) {},
suspense: false,
// By default is `false`
skip: false,
}
);
if (isLoading) {
return <p>Loading...</p>;
}
if (error) {
return <p>Error! {error.message}</p>;
}
return <p>{data}</p>;
}
useLazyQuery#
Queries meant to be called in response of events, like button clicks.
Features#
- Lifecycle functions
onCompleted
&onError
- Suspense support
- Partial Fetch policy support (no
'cache-first'
)
import { useLazyQuery } from '../gqty';
function Example() {
const [getName, { isLoading, data }] = useLazyQuery(
(query, name: string) => {
return query.hello({ name });
},
{
onCompleted(data) {},
onError(error) {},
// Default is 'network-only'
fetchPolicy: 'cache-and-network',
retry: false,
suspense: false,
}
);
if (isLoading) {
return <p>Loading...</p>;
}
if (data) {
return <p>{data}</p>;
}
return (
<button
onClick={() =>
getName({
args: 'John',
})
}
>
Get Name
</button>
);
}
useRefetch#
Refetch giving specific parts of the schema or via functions
Example with functions#
import { useRefetch, useQuery } from '../gqty';
function Example() {
const refetch = useRefetch();
const query = useQuery();
return (
<div>
<button
onClick={() => {
refetch(() => query.helloWorld);
}}
>
Refetch
</button>
<p>{query.helloWorld}</p>
</div>
);
}
Example with objects#
In this case, you have to specify an object type, scalars won't work (for those, you have to use functions).
It will automatically refetch everything previously requested under that tree
import { useRefetch, useQuery } from '../gqty';
function Example() {
const refetch = useRefetch();
const query = useQuery();
return (
<div>
<button
onClick={() => {
// It will refetch both, 'name' & 'email'
refetch(user);
}}
>
Refetch
</button>
<p>{query.user.name}</p>
<p>{query.user.email}</p>
</div>
);
}
prepareQuery#
Prepare queries on module-level.
Features#
- Enable Render as you fetch pattern
- Re-using the callbacks in useTransactionQuery & useLazyQuery
- Composition
- Prefetching
- Refetching
- Suspense Support
Example#
import { useState } from 'react';
import { prepareQuery } from '../gqty';
const { preload, refetch, usePrepared, callback } = prepareQuery((query) => {
return query.helloWorld;
});
function Example() {
const { data } = usePrepared();
return <p>{data}</p>;
}
function Container() {
const [show, setShow] = useState(false);
return (
<div>
<button
onClick={() => {
if (show) {
refetch();
} else {
preload();
}
}}
>
{show ? 'Refetch' : 'Show Example'}
</button>
{show && <Example />}
</div>
);
}
Fetch Policy#
Fetch policies allow you to have fine-grained control over your fetch behaviors
Name | Description |
---|---|
cache-first | Client executes the query against the cache, if all requested data is present in the cache, that data is returned. Otherwise, it executes the query against your GraphQL server and returns the data after caching it. Prioritizes minimizing the number of network requests sent by your application. |
cache-and-network | Client executes the full query against both the cache and your GraphQL server. Provides a fast response while also helping to keep cached data consistent with server data. |
network-only | Client executes the full query against your GraphQL server, without first checking the cache. The query's result is stored in the cache. |
no-cache | Client executes the full query against your GraphQL server, without first checking the cache. The query's result is NOT stored in the cache. |