New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

graphql-hooks

Package Overview
Dependencies
Maintainers
1
Versions
89
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

graphql-hooks - npm Package Compare versions

Comparing version 2.0.0 to 2.0.1

.editorconfig

11

package.json
{
"name": "graphql-hooks",
"version": "2.0.0",
"version": "2.0.1",
"description": "Graphql Hooks",

@@ -33,6 +33,7 @@ "main": "src/index.js",

"jest": "24.1.0",
"jest-fetch-mock": "2.1.1",
"prettier": "1.16.4",
"pretty-quick": "1.10.0",
"react": "16.8.1",
"react-dom": "16.8.1",
"react": "16.8.2",
"react-dom": "16.8.2",
"react-testing-library": "5.8.0"

@@ -49,2 +50,6 @@ },

"jest": {
"automock": false,
"setupFiles": [
"./test/setup.js"
],
"setupFilesAfterEnv": [

@@ -51,0 +56,0 @@ "react-testing-library/cleanup-after-each"

@@ -5,4 +5,6 @@ # graphql-hooks

[![Coverage Status](https://coveralls.io/repos/github/nearform/graphql-hooks/badge.svg?branch=master)](https://coveralls.io/github/nearform/graphql-hooks?branch=master)
![](https://img.shields.io/bundlephobia/minzip/graphql-hooks.svg?style=flat)
[![npm version](https://badge.fury.io/js/graphql-hooks.svg)](https://badge.fury.io/js/graphql-hooks)
🎣 Minimal hooks-first graphql client.
🎣 Minimal hooks-first GraphQL client.

@@ -12,5 +14,6 @@ ## Features

- 🥇 First-class hooks API
- ⚖️ _Tiny_ bundle: only 3.7kB (1.4 gzipped)
- 📄 Full SSR support: see [graphql-hooks-ssr](https://github.com/nearform/graphql-hooks-ssr)
- 🔌 Plugin Caching: see [graphql-hooks-memcache](https://github.com/nearform/graphql-hooks-memcache)
- 🔥 No more render props hell
- ⚖️ Lightweight; only what you really need
- ️️♻️ Promise-based API (works with `async` / `await`)
- ⏳ Handle loading and error states with ease

@@ -78,8 +81,7 @@

## TOC
# Table of Contents
- APIs
- API
- [GraphQLClient](#GraphQLClient)
- [ClientContext](#ClientContext)
- [useClient](#useClient)
- [useQuery](#useQuery)

@@ -91,19 +93,218 @@ - [useManualQuery](#useManualQuery)

- [Authentication](#Authentication)
- [Refetching a query](#Refetching-a-query)
- [Fragments](#Fragments)
- [Migrating from Apollo](#Migrating-from-Apollo)
## API
### `GraphQLClient`
## `GraphQLClient`
### `ClientContext`
**Usage**:
### `useClient`
```js
import { GraphQLClient } from 'graphql-hooks';
const client = new GraphQLClient(config);
```
### `useQuery`
**`config`**: Object containing configuration properties
### `useManualQuery`
- `url` (**Required**): The url to your GraphQL server
- `ssrMode`: Boolean - set to `true` when using on the server for server-side rendering; defaults to `false`
- `cache`: Object with the following methods:
- `cache.get(key)`
- `cache.set(key, data)`
- `cache.delete(key)`
- `cache.clear()`
- `cache.keys()`
- `getInitialState()`
- See [graphql-hooks-memcache](https://github.com/nearform/graphql-hooks-memcache) as a reference implementation
- `fetch(url, options)`: Fetch implementation - defaults to the global `fetch` API
- `fetchOptions`: See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch) for info on what options can be passed
- `headers`: Object, e.g. `{ 'My-Header': 'hello' }`
- `logErrors`: Boolean - defaults to `true`
- `onError({ operation, result })`: Custom error handler
- `operation`: Object with `query`, `variables` and `operationName`
- `result`: Object containing `error`, `data`, `fetchError`, `httpError` and `graphqlErrors`
### `useMutation`
### `client` methods
- `client.setHeader(key, value)`: Updates `client.headers` adding the new header to the existing headers
- `client.setHeaders(headers)`: Replaces `client.headers`
- `client.logErrorResult({ operation, result })`: Default error logger; useful if you'd like to use it inside your custom `onError` handler
- `request(operation, options)`: Make a request to your GraphQL server; returning a Promise
- `operation`: Object with `query`, `variables` and `operationName`
- `options.fetchOptionsOverrides`: Object containing additional fetch options to be added to the default ones passed to `new GraphQLClient(config)`
## `ClientContext`
`ClientContext` is the result of `React.createContext()` - meaning it can be used directly with React's new context API:
**Example**:
```js
import { ClientContext } from 'graphql-hooks';
<ClientContext.Provider value={client}>
{/* children can now consume the client context */}
</ClientContext.Provider>;
```
To access the `GraphQLClient` instance, call `React.useContext(ClientContext)`:
```js
import React, { useContext } from 'react';
import { ClientContext } from 'graphql-hooks';
function MyComponent() {
const client = useContext(ClientContext);
}
```
## `useQuery`
**Usage**:
```js
const state = useQuery(query, [options]);
```
**Example:**
```js
import { useQuery } from 'graphql-hooks';
function MyComponent() {
const { loading, error, data } = useQuery(query);
if (loading) return 'Loading...';
if (error) return 'Something bad happened';
return <div>{data.thing}</div>;
}
```
This is a custom hook that takes care of fetching your query and storing the result in the cache. It won't refetch the query unless `query` or `options.variables` changes.
- `query`: Your GraphQL query as a plain string
- `options`: Object with the following optional properties
- `variables`: Object e.g. `{ limit: 10 }`
- `operationName`: If your query has multiple operations, pass the name of the operation you wish to execute.
- `useCache`: Boolean - defaults to `true`; cache the query result
- `skipCache`: Boolean - defaults to `false`; If `true` it will by-pass the cache and fetch, but the result will then be cached for subsequent calls. Note the `refetch` function will do this automatically
- `ssr`: Boolean - defaults to `true`. Set to `false` if you wish to skip this query during SSR
- `fetchOptionsOverrides`: Object - Specific overrides for this query. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch) for info on what options can be passed
### `useQuery` return value
```js
const { loading, error, data, refetch, cacheHit, ...errors } = useQuery(QUERY);
```
- `loading`: Boolean - `true` if the query is in flight
- `error`: Boolean - `true` if `fetchError` or `httpError` or `graphQLErrors` has been set
- `data`: Object - the result of your GraphQL query
- `refetch`: Function - useful when refetching the same query after a mutation; NOTE this presets `skipCache=true`
- `cacheHit`: Boolean - `true` if the query result came from the cache, useful for debugging
- `fetchError`: Object - Set if an error occured during the `fetch` call
- `httpError`: Object - Set if an error response was returned from the server
- `graphQLErrors`: Array - Populated if any errors occured whilst resolving the query
## `useManualQuery`
Use this when you don't want a query to automactially be fetched, or wish to call a query programmatically.
**Usage**:
```js
const [queryFn, state] = useManualQuery(query, [options]);
```
**Example**:
```js
import { useManualQuery } from 'graphql-hooks'
function MyComponent(props) {
const [fetchUser, { loading, error, data }] = useManualQuery(GET_USER_QUERY, {
variables: { id: props.userId }
})
return (
<div>
<button onClick={fetchUser}>Get User!</button>
{error && <div>Failed to fetch user<div>}
{loading && <div>Loading...</div>}
{data && <div>Hello ${data.user.name}</div>}
</div>
)
}
```
If you don't know certain options when declaring the `useManualQuery` you can also pass the same options to the query function itself when calling it:
```js
import { useManualQuery } from 'graphql-hooks';
function MyComponent(props) {
const [fetchUser] = useManualQuery(GET_USER_QUERY);
const fetchUserThenSomething = async () => {
const user = await fetchUser({
variables: { id: props.userId }
});
return somethingElse();
};
return (
<div>
<button onClick={fetchUserThenSomething}>Get User!</button>
</div>
);
}
```
## `useMutation`
Mutations unlike Queries are not cached.
**Usage**:
```js
const [mutationFn, state] = useMutation(mutation, [options]);
```
**Example**:
```js
import { useMutation } from 'graphql-hooks';
const UPDATE_USER_MUTATION = `mutation UpdateUser(id: String!, name: String!) {
updateUser(id: $id, name: $name) {
name
}
}`;
function MyComponent({ id, name }) {
const [updateUser] = useMutation(UPDATE_USER_MUTATION);
const [newName, setNewName] = useState(name);
return (
<div>
<input
type="text"
value={newName}
onChange={e => setNewName(e.target.value)}
/>
<button
onClick={() => updateUser({ variables: { id, name: newName } })}
/>
</div>
);
}
```
The `options` object that can be passed either to `useMutation(mutation, options)` or `mutationFn(options)` can be set with the following properties:
- `variables`: Object e.g. `{ limit: 10 }`
- `operationName`: If your query has multiple operations, pass the name of the operation you wish to execute.
- `fetchOptionsOverrides`: Object - Specific overrides for this query. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch) for info on what options can be passed
## Guides

@@ -113,6 +314,14 @@

See [graphql-hooks-ssr](https://github.com/nearform/graphql-hooks-ssr) for an in depth guide.
### Authentication
### Refetching a query
Coming soon!
### Fragments
Coming soon!
### Migrating from Apollo
Coming soon!
class GraphQLClient {
constructor(config) {
constructor(config = {}) {
// validate config
if (!config.url) {
throw new Error('GraphQLClient: config.url is required');
}
if (config.fetch && typeof config.fetch !== 'function') {

@@ -92,3 +96,3 @@ throw new Error('GraphQLClient: config.fetch must be a function');

async request(operation, options) {
async request(operation, options = {}) {
let result;

@@ -95,0 +99,0 @@

@@ -0,5 +1,301 @@

import fetchMock from 'jest-fetch-mock';
import { GraphQLClient } from '../../src';
const validConfig = {
url: 'https://my.graphql.api'
};
const TEST_QUERY = `query Test($limit: Int) {
tests(limit: $limit) {
id
}
}`;
describe('GraphQLClient', () => {
it('runs a test', () => {});
describe('when instantiated', () => {
it('throws if no url provided', () => {
expect(() => {
new GraphQLClient();
}).toThrow('GraphQLClient: config.url is required');
});
it('throws if fetch is not a function', () => {
expect(() => {
new GraphQLClient({ ...validConfig, fetch: 'fetch!' });
}).toThrow('GraphQLClient: config.fetch must be a function');
});
it('throws if fetch is not present or polyfilled', () => {
const oldFetch = global.fetch;
global.fetch = null;
expect(() => {
new GraphQLClient(validConfig);
}).toThrow(
'GraphQLClient: fetch must be polyfilled or passed in new GraphQLClient({ fetch })'
);
global.fetch = oldFetch;
});
it('assigns config.cache to an instance property', () => {
const cache = { get: 'get', set: 'set' };
const client = new GraphQLClient({ ...validConfig, cache });
expect(client.cache).toBe(cache);
});
it('assigns config.headers to an instance property', () => {
const headers = { 'My-Header': 'hello' };
const client = new GraphQLClient({ ...validConfig, headers });
expect(client.headers).toBe(headers);
});
it('assigns config.ssrMode to an instance property', () => {
const client = new GraphQLClient({ ...validConfig, ssrMode: true });
expect(client.ssrMode).toBe(true);
});
it('assigns config.url to an instance property', () => {
const client = new GraphQLClient({ ...validConfig });
expect(client.url).toBe(validConfig.url);
});
it('assigns config.fetch to an instance property', () => {
const myFetch = jest.fn();
const client = new GraphQLClient({ ...validConfig, fetch: myFetch });
expect(client.fetch).toBe(myFetch);
});
it('assigns config.fetchOptions to an instance property', () => {
const fetchOptions = { fetch: 'options' };
const client = new GraphQLClient({ ...validConfig, fetchOptions });
expect(client.fetchOptions).toBe(fetchOptions);
});
it('assigns config.logErrors to an instance property', () => {
const client = new GraphQLClient({ ...validConfig, logErrors: true });
expect(client.logErrors).toBe(true);
});
it('assigns config.onError to an instance property', () => {
const onError = jest.fn();
const client = new GraphQLClient({ ...validConfig, onError });
expect(client.onError).toBe(onError);
});
});
describe('setHeader', () => {
it('sets the key to the value', () => {
const client = new GraphQLClient({ ...validConfig });
client.setHeader('My-Header', 'hello');
expect(client.headers['My-Header']).toBe('hello');
});
});
describe('setHeaders', () => {
it('replaces all headers', () => {
const headers = { 'My-Header': 'hello ' };
const client = new GraphQLClient({ ...validConfig });
client.setHeaders(headers);
expect(client.headers).toBe(headers);
});
});
describe('logErrorResult', () => {
let logSpy, errorSpy, groupCollapsedSpy, groupEndSpy;
beforeEach(() => {
logSpy = spyOn(global.console, 'log');
errorSpy = spyOn(global.console, 'error');
groupCollapsedSpy = spyOn(global.console, 'groupCollapsed');
groupEndSpy = spyOn(global.console, 'groupEnd');
});
afterEach(() => {
jest.restoreAllMocks();
});
it('calls onError if present', () => {
const onError = jest.fn();
const client = new GraphQLClient({ ...validConfig, onError });
client.logErrorResult({ result: 'result', operation: 'operation' });
expect(onError).toHaveBeenCalledWith({
result: 'result',
operation: 'operation'
});
});
it('logs a fetchError', () => {
const client = new GraphQLClient({ ...validConfig });
client.logErrorResult({ result: { fetchError: 'on no fetch!' } });
expect(groupCollapsedSpy).toHaveBeenCalledWith('FETCH ERROR:');
expect(logSpy).toHaveBeenCalledWith('on no fetch!');
expect(groupEndSpy).toHaveBeenCalled();
});
it('logs an httpError', () => {
const client = new GraphQLClient({ ...validConfig });
client.logErrorResult({ result: { httpError: 'on no http!' } });
expect(groupCollapsedSpy).toHaveBeenCalledWith('HTTP ERROR:');
expect(logSpy).toHaveBeenCalledWith('on no http!');
expect(groupEndSpy).toHaveBeenCalled();
});
it('logs all graphQLErrors', () => {
const client = new GraphQLClient({ ...validConfig });
const graphQLErrors = ['on no GraphQL!', 'oops GraphQL!'];
client.logErrorResult({ result: { graphQLErrors } });
expect(groupCollapsedSpy).toHaveBeenCalledWith('GRAPHQL ERROR:');
expect(logSpy).toHaveBeenCalledWith('on no GraphQL!');
expect(logSpy).toHaveBeenCalledWith('oops GraphQL!');
expect(groupEndSpy).toHaveBeenCalled();
});
});
describe('generateResult', () => {
it('shows as errored if there are graphQL errors', () => {
const client = new GraphQLClient({ ...validConfig });
const result = client.generateResult({
graphQLErrors: ['error 1', 'error 2']
});
expect(result.error).toBe(true);
});
it('shows as errored if there is a fetch error', () => {
const client = new GraphQLClient({ ...validConfig });
const result = client.generateResult({
fetchError: 'fetch error'
});
expect(result.error).toBe(true);
});
it('shows as errored if there is an http error', () => {
const client = new GraphQLClient({ ...validConfig });
const result = client.generateResult({
httpError: 'http error'
});
expect(result.error).toBe(true);
});
it('returns the errors & data', () => {
const client = new GraphQLClient({ ...validConfig });
const data = {
graphQLErrors: ['graphQL error 1', 'graphQL error 2'],
fetchError: 'fetch error',
httpError: 'http error',
data: 'data!'
};
const result = client.generateResult(data);
expect(result).toEqual({
error: true,
graphQLErrors: data.graphQLErrors,
fetchError: data.fetchError,
httpError: data.httpError,
data: data.data
});
});
});
describe('getCacheKey', () => {
it('returns a cache key', () => {
const client = new GraphQLClient({
...validConfig,
fetchOptions: { optionOne: 1 }
});
const cacheKey = client.getCacheKey('operation', {
fetchOptionsOverrides: { optionTwo: 2 }
});
expect(cacheKey).toEqual({
operation: 'operation',
fetchOptions: { optionOne: 1, optionTwo: 2 }
});
});
});
describe('request', () => {
afterEach(() => {
fetch.resetMocks();
});
it('sends the request to the configured url', async () => {
const client = new GraphQLClient({ ...validConfig });
fetch.mockResponseOnce(JSON.stringify({ data: 'data' }));
await client.request({ query: TEST_QUERY });
const actual = fetch.mock.calls[0][0];
const expected = validConfig.url;
expect(actual).toBe(expected);
});
it('applies the configured headers', async () => {
const headers = { 'My-Header': 'hello' };
const client = new GraphQLClient({ ...validConfig, headers });
fetch.mockResponseOnce(JSON.stringify({ data: 'data' }));
await client.request({ query: TEST_QUERY });
const actual = fetch.mock.calls[0][1].headers['My-Header'];
const expected = 'hello';
expect(actual).toBe(expected);
});
it('sends the provided operation query, variables and name', async () => {
const client = new GraphQLClient({ ...validConfig });
fetch.mockResponseOnce(JSON.stringify({ data: 'data' }));
const operation = {
query: TEST_QUERY,
variables: { limit: 1 },
operationName: 'test'
};
await client.request(operation);
const actual = fetch.mock.calls[0][1].body;
const expected = JSON.stringify(operation);
expect(actual).toBe(expected);
});
it('handles & returns fetch errors', async () => {
const client = new GraphQLClient({ ...validConfig });
client.logErrorResult = jest.fn();
const error = new Error('Oops fetch!');
fetch.mockRejectOnce(error);
const res = await client.request({ query: TEST_QUERY });
expect(res.fetchError).toBe(error);
});
it('handles & returns http errors', async () => {
const client = new GraphQLClient({ ...validConfig });
client.logErrorResult = jest.fn();
fetch.mockResponseOnce('Denied!', {
status: 403
});
const res = await client.request({ query: TEST_QUERY });
expect(res.httpError).toEqual({
status: 403,
statusText: 'Forbidden',
body: 'Denied!'
});
});
it('returns valid responses', async () => {
const client = new GraphQLClient({ ...validConfig });
fetch.mockResponseOnce(JSON.stringify({ data: 'data!' }));
const res = await client.request({ query: TEST_QUERY });
expect(res.data).toBe('data!');
});
it('returns graphql errors', async () => {
const client = new GraphQLClient({ ...validConfig });
client.logErrorResult = jest.fn();
fetch.mockResponseOnce(
JSON.stringify({ data: 'data!', errors: ['oops!'] })
);
const res = await client.request({ query: TEST_QUERY });
expect(res.graphQLErrors).toEqual(['oops!']);
});
it('will use a configured fetch implementation', async () => {
fetchMock.mockResponseOnce(JSON.stringify({ data: 'data' }));
const client = new GraphQLClient({ ...validConfig, fetch: fetchMock });
await client.request({ query: TEST_QUERY });
expect(fetchMock).toHaveBeenCalled();
});
});
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc