npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

gqless-hooks

v2.0.1

Published

[![npm version](https://badge.fury.io/js/gqless-hooks.svg)](https://badge.fury.io/js/gqless-hooks) [![bundlephobia](https://badgen.net/bundlephobia/minzip/gqless-hooks)](https://bundlephobia.com/result?p=gqless-hooks) [![license](https://badgen.net/github

Downloads

10

Readme

gqless-hooks

npm version bundlephobia license combined checks codecov

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.

If you are not familiar with gqless please check https://gqless.dev/

Table of Contents

Usage

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.
};

Features

  • Cache policies, somewhat following Apollo fetchPolicy
  • Shared global cache.
  • Polling
  • Automatic refetch on variables change
  • Support for Pagination with a fetchMore callback.
  • Server side rendering support (with usage examples for Next.js)
  • Prefetching support

Docs and API Reference

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

Usage tips

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,
    },
  }
);

Headers

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: '...',
    },
  }
);

Polling

You can set a polling interval in milliseconds

useQuery(
  (schema) => {
    //...
  },
  {
    //...
    pollInterval: 100,
  }
);

Shared cache and in memory persistence

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',
  }
);

Pagination

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],
      };
    },
  });
}

getAccessorFields | getArrayAccessorFields

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

prepareQuery (SSR, prefetching, refetch, type-safety)

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:

  • The query function.
  • The cacheId.
  • An async function called prepare.
  • A React Cache Hydration Hook useHydrateCache.
  • A useQuery shorthand hook that already includes the query and the cacheId.
  • A shorthand setCacheData function to manually update the cache and hooks data.
  • A TypeScript-only 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>;
};

Fully featured examples

About it

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.

Future

  • Add more examples of usage
  • Suspense support
  • Add support for Subscriptions

Contributing

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.