yarn add gqless-hooks
# or
npm install gqless-hooks
This library creates a couple of hooks to interact with gqless, all while being type-safe.
Table of Contents
This library should ideally be imported and used at src/graphql/client.ts (this is default location, could be anywhere you previously set it up)
import { Client, QueryFetcher } from 'gqless';
import { Mutation, Query, schema } from './generated';
import { createUseMutation, createUseQuery } from 'gqless-hooks';
const endpoint = '...';
// ...
export const query = client.query;
export const useMutation = createUseMutation<Mutation>({
endpoint,
schema,
});
export const { useQuery, prepareQuery } = createUseQuery<Query>({
endpoint,
schema,
});
Then anywhere you want to use them, you import them just like the default vanilla query.
import { useMutation, useQuery } from '../src/graphql';
const Component = () => {
const [helloWorldMutation, helloWorldData] = useMutation(
({ helloWorldMutation }) => {
const { id, label } = helloWorldMutation({ arg: 'hello' });
return { id, label };
}
);
// helloWorldData === { data, state = "loading" | "error" | "waiting" | "done", errors = GraphqlError[] | undefined }
// helloWorldMutation works as initiator of the mutation or recall.
const [helloWorldData, { callback, refetch, cacheRefetch }] = useQuery(
({ helloWorldQuery: { id, label } }) => ({ id, label }),
{
// if lazy == true, wait until function from returned array is called
lazy: true,
}
);
// helloWorldData === { data = { id,label } | undefined | null, state = "loading" | "error" | "waiting" | "done", errors = GraphqlError[] | undefined }
// callback and refetch work as initiators of the query or refetch.
};
You can check https://pabloszx.github.io/gqless-hooks/ for some documentation and API Reference, all generated through it's strong type-safety using TypeDoc.
Also keep in mind that these hooks are heavily inspired by React Apollo GraphQL
Due to how gqless works, in the query and mutation hook functions, when you return some data, you have to explicitly access it's properties for it to detect it's requirements, this means in practice that if you have an object, you have to explictly explore its properties (destructuring for example) and return them, and for arrays is the same, but for them it's recommended to use array.map(...).
For example
useQuery(
(schema, variables) => {
// variables === { b: 2 }
const { field1, field2 } = schema.helloWorldObj;
return { field1, field2 };
// return helloWorldObj; <- would return an empty object
},
{
variables: {
b: 2,
},
}
);
useQuery(
(schema, variables) => {
// variables === { a: 1 }
const array = schema.helloWorldArray;
return array.map(({ fieldA, fieldB }) => ({ fieldA, fieldB }));
// return array; <- would return an empty array
},
{
variables: {
a: 1,
},
}
);
You can set headers to be added to every fetch call
export const useQuery = createUseQuery<Query>({
schema,
endpoint,
creationHeaders: {
authorization: '...',
},
});
or individually
//useMutation((schema) => {
useQuery(
(schema) => {
//...
},
{
//...
headers: {
authorization: '...',
},
}
);
You can set a polling interval in milliseconds
useQuery(
(schema) => {
//...
},
{
//...
pollInterval: 100,
}
);
You can specify that some hooks actually refer to the same data, and for that you can specify a sharedCacheId that will automatically synchronize the hooks data, or persist in memory hooks data.
Be careful and make sure the synchronized hooks share the same data type signature
// useMutation((schema) => {
useQuery(
(schema) => {
//...
},
{
//...
sharedCacheId: 'hook1',
}
);
// another component
// useMutation((schema) => {
useQuery(
(schema) => {
//...
},
{
// You could also specify the cache-only fetchPolicy
// To optimize the hook and prevent unwanted
// network fetches.
fetchPolicy: 'cache-only',
//...
sharedCacheId: 'hook1',
}
);
You also can manipulate the shared cache directly using setCacheData
and prevent unnecessary network calls or synchronize different hooks.
import { setCacheData } from 'gqless-hooks';
// This declaration is optional type-safety
declare global {
interface gqlessSharedCache {
hookKey1: string[];
}
}
setCacheData('hookKey1', ['hello', 'world']);
// ...
useQuery(
(schema) => {
// ...
},
{
// ...
sharedCacheId: 'hookKey1',
}
);
For pagination you can use fetchMore from useQuery, somewhat following Apollo fetchMore API.
const [{ data }, { fetchMore }] = useQuery(
(schema, { skip, limit }) => {
const {
nodes,
pageInfo: { hasNext },
} = schema.feed({
skip,
limit,
});
return {
nodes: nodes.map(({ _id, title }) => {
return {
_id,
title,
};
}),
pageInfo: {
hasNext,
},
};
},
{
variables: {
skip: 0,
limit: 5,
},
}
);
// ...
if (data?.hasNext) {
const newData = await fetchMore({
variables: {
skip: data.length,
},
updateQuery(previousResult, newResult) {
if (!newResult) return previousResult;
// Here you are handling the raw data, not "accessors"
return {
pageInfo: newResult.pageInfo,
nodes: [...(previousResult?.nodes ?? []), ...newResult.nodes],
};
},
});
}
When using this library there is a common pattern in the schema -> query functions which is just destructuring the data you need from the query, the problem is that it tends to be very repetitive, and for that this library exports a couple of utility functions that help with this problem.
These functions are designed to help with autocomplete and type-safety.
Keep in mind that these functions are composable.
import { getAccessorFields } from 'gqless-hooks';
useQuery((schema, variables) => {
// This is the long way
// const { title, content, publishedData } =
// schema.blog({ id: variables.id });
// return { title, content, publishedData };
// This is the quicker way
return getAccessorFields(
schema.blog({ id: variables.id }),
'title',
'content',
'publishedDate'
);
});
import { getArrayAccessorFields } from 'gqless-hooks';
useQuery((schema) => {
// This is the long way
// return schema.blogList.map({ title, content, publishedData }
// => ({ title, content, publishedData }));
// This is the quicker way
return getArrayAccessorFields(
schema.blogList,
'title',
'content',
'publishedData'
);
});
You can use prepareQuery generated from createUseQuery, in which you give it a unique cache identifier
and the schema -> query
function, and it returns an object containing:
query
function.cacheId
.prepare
.useHydrateCache
.useQuery
shorthand hook that already includes the query and the cacheId.setCacheData
function to manually update the cache and hooks data.dataType
helper.Keep in mind that the example as follows uses prepare as a SSR helper, but you could also use it client side for prefetching or refetching, and/or use the checkCache boolean argument option.
This example is using Next.js getServerSideProps, but follows the same API for getStaticProps or any other implementation.
import { NextPage, GetServerSideProps } from 'next';
import { prepareQuery, useQuery } from '../src/graphql';
const HelloQuery = prepareQuery({
cacheId: 'helloWorld',
query: (schema) => {
return schema.hello({ arg: 'world' });
},
});
interface HelloWorldProps {
helloWorld: typeof HelloQuery.dataType;
}
export const getServerSideProps: GetServerSideProps<HelloWorldProps> = async () => {
const helloWorld = await HelloQuery.prepare();
return {
props: {
helloWorld,
},
};
};
const HelloPage: NextPage<HelloWorldProps> = (props) => {
// This hydrates the cache and prevents network requests.
HelloQuery.useHydrateCache(props.helloWorld);
const [{ data }] = HelloQuery.useQuery();
return <div>{JSON.stringify(data, null, 2)}</div>;
};
Blog administration panel inside a Next.js / Nexus / Mongoose / gqless-hooks project.
These hooks are a proof of concept that ended up working and is a good workaround until React Suspense is officially released (with good SSR support), along with the lack of functionality out of the box of the official gqless API, and of course, Mutation is officially supported by gqless.
If you are only using these hooks and not the default query from gqless, you don't need to use the graphql HOC, and it means less bundle size.
Everyone is more than welcome to help in this project, there is a lot of work still to do to improve this library, but I hope it's useful, as it has been while I personally use it for some of my new web development projects.
Generated using TypeDoc