Skip to main content

Fetching data with React

useQuery#

React hook that uses the main design of GQless.

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.

Check API Reference

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 for Non-Suspense usage.

Suspense Example#

import { Suspense } from 'react';
import { useQuery } from '../gqless';
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: GQlessError) => 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 '../gqless';
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: GQlessError) => 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.
caution

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 '../gqless';
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 '../gqless';
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 '../gqless';
const Example = graphql(
function Example() {
return <p>{query.helloWorld}</p>;
},
{
// boolean | { fallback: NonNullable<ReactNode> | null } | undefined
suspense: true,
// ((error: GQlessError) => void) | undefined
onError(error) {},
// boolean | undefined
staleWhileRevalidate: true,
}
);
function Container() {
return (
<div>
<Suspense fallback="Loading...">
<Example />
</Suspense>
</div>
);
}

Check API Reference

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 of fetchMore, for example, to force a refetch.
  • The helper functions are included in third parameter of main function.

Example#

import { usePaginatedQuery, ConnectionArgs } from '../gqless';
// ...
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>
);
}

Check API Reference

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 '../gqless';
// ...
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>;
}

Check API Reference

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 '../gqless';
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>
);
}

Check API Reference

useRefetch#

Refetch giving specific parts of the schema or via functions

Check API Reference

Example with functions#

import { useRefetch, useQuery } from '../gqless';
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 '../gqless';
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#

Example#

import { useState } from 'react';
import { prepareQuery } from '../gqless';
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>
);
}

Check API Reference

Fetch Policy#

Fetch policies allow you to have fine-grained control over your fetch behaviors

NameDescription
cache-firstClient 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-networkClient 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-onlyClient executes the full query against your GraphQL server, without first checking the cache.

The query's result is stored in the cache.
no-cacheClient executes the full query against your GraphQL server, without first checking the cache.

The query's result is NOT stored in the cache.
Last updated on by github-actions[bot]