Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(feat) - allow passing operationContext to useQuery #351

Merged
merged 10 commits into from
Aug 1, 2019
4 changes: 4 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ interface UseQueryArgs {
variables?: any;
requestPolicy?: RequestPolicy;
pause?: boolean;
context?: Partial<OperationContext>;
}
```

Expand Down Expand Up @@ -69,6 +70,7 @@ The options argument shape is:
interface UseSubscriptionArgs {
query: string;
variables?: any;
context?: Partial<OperationContext>;
}
```

Expand Down Expand Up @@ -103,6 +105,7 @@ interface UseSubscriptionState<T> {
| ------------- | -------------------------- | ----------------------------------------------------------------------------------------------------- |
| query | `string` | The GraphQL request's query |
| variables | `object` | The GraphQL request's variables |
| context | `?object` | The GraphQL request's context |
| requestPolicy | `?RequestPolicy` | An optional request policy that should be used |
| pause | `?boolean` | A boolean flag instructing `Query` to pause execution of the subsequent query operation |
| children | `RenderProps => ReactNode` | A function that follows the typical render props pattern. The shape of the render props is as follows |
Expand Down Expand Up @@ -148,6 +151,7 @@ interface UseSubscriptionState<T> {
| --------- | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| query | `string` | The GraphQL subscription's query |
| variables | `object` | The GraphQL subscriptions' variables |
| context | `?object` | The GraphQL subscriptions' context |
| handler | `undefined \| (prev: R \| undefined, data: T) => R` | The handler that should combine/update the subscription's data with incoming data |
| children | `RenderProps => ReactNode` | A function that follows the typical render props pattern. The shape of the render props is as follows |

Expand Down
7 changes: 7 additions & 0 deletions docs/extending-and-experimenting.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,10 @@ subscribe to the result of `executeQuery`, keep track
of the unsubscription (`teardown`) and update
some state. This all can be reapplied when you write your
own APIs.

## FAQ

My component/hooks keeps triggering rerenders when passing in `context`

- When not memoizing `context` this will always trigger refetches,
this can be done through the `useMemo` hook.
7 changes: 5 additions & 2 deletions src/components/Mutation.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ReactElement } from 'react';
import { DocumentNode } from 'graphql';
import { OperationResult } from '../types';
import { OperationResult, OperationContext } from '../types';
import { useMutation, UseMutationState } from '../hooks';

export interface MutationProps<T, V> {
Expand All @@ -9,7 +9,10 @@ export interface MutationProps<T, V> {
}

export interface MutationState<T, V> extends UseMutationState<T> {
executeMutation: (variables?: V) => Promise<OperationResult<T>>;
executeMutation: (
variables?: V,
context?: Partial<OperationContext>
) => Promise<OperationResult<T>>;
}

export function Mutation<T = any, V = any>({
Expand Down
8 changes: 8 additions & 0 deletions src/hooks/useMutation.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ describe('on execute', () => {
source: 'MutationUser',
});
});

it('can adjust context in executeMutation', () => {
renderer.create(<MutationUser {...props} />);
act(() => {
execute(vars, { url: 'test' });
});
expect(client.executeMutation.mock.calls[0][1].url).toBe('test');
});
});

describe('on subscription update', () => {
Expand Down
6 changes: 3 additions & 3 deletions src/hooks/useMutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { DocumentNode } from 'graphql';
import { useContext, useCallback } from 'react';
import { pipe, toPromise } from 'wonka';
import { Context } from '../context';
import { OperationResult } from '../types';
import { OperationResult, OperationContext } from '../types';
import { CombinedError, createRequest } from '../utils';
import { useImmediateState } from './useImmediateState';
import { useDevtoolsContext } from './useDevtoolsContext';
Expand Down Expand Up @@ -32,7 +32,7 @@ export const useMutation = <T = any, V = object>(
});

const executeMutation = useCallback(
(variables?: V) => {
(variables?: V, context?: Partial<OperationContext>) => {
setState({
fetching: true,
error: undefined,
Expand All @@ -43,7 +43,7 @@ export const useMutation = <T = any, V = object>(
const request = createRequest(query, variables as any);

return pipe(
client.executeMutation(request, devtoolsContext),
client.executeMutation(request, { ...devtoolsContext, ...context }),
toPromise
).then(result => {
const { data, error, extensions } = result;
Expand Down
21 changes: 21 additions & 0 deletions src/hooks/useQuery.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,27 @@ describe('useQuery', () => {
});
});

it('should support setting context in useQuery params', () => {
const context = { url: 'test' };
renderHook(
({ query, variables }) => useQuery({ query, variables, context }),
{ initialProps: { query: mockQuery, variables: mockVariables } }
);

expect(client.executeQuery).toBeCalledWith(
{
key: expect.any(Number),
query: expect.any(Object),
variables: mockVariables,
},
{
meta: { source: 'TestHook' },
requestPolicy: undefined,
url: 'test',
}
);
});

it('should execute the subscription', async () => {
const { waitForNextUpdate } = renderHook(
({ query, variables }) => useQuery({ query, variables }),
Expand Down
11 changes: 10 additions & 1 deletion src/hooks/useQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface UseQueryArgs<V> {
query: string | DocumentNode;
variables?: V;
requestPolicy?: RequestPolicy;
context?: Partial<OperationContext>;
pause?: boolean;
}

Expand Down Expand Up @@ -57,6 +58,7 @@ export const useQuery = <T = any, V = object>(
[unsubscribe.current] = pipe(
client.executeQuery(request, {
requestPolicy: args.requestPolicy,
...args.context,
...opts,
...devtoolsContext,
}),
Expand All @@ -65,7 +67,14 @@ export const useQuery = <T = any, V = object>(
})
);
},
[args.requestPolicy, client, devtoolsContext, request, setState]
[
args.context,
args.requestPolicy,
client,
devtoolsContext,
request,
setState,
]
);

useImmediateEffect(() => {
Expand Down
26 changes: 23 additions & 3 deletions src/hooks/useSubscription.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ jest.mock('../client', () => {
});

import React, { FC } from 'react';
import renderer from 'react-test-renderer';
import renderer, { act } from 'react-test-renderer';
// @ts-ignore - data is imported from mock only
import { createClient, data } from '../client';
import { useSubscription } from './useSubscription';
import { OperationContext } from '../types';

// @ts-ignore
const client = createClient() as { executeSubscription: jest.Mock };
Expand All @@ -27,8 +28,9 @@ let state: any;
const SubscriptionUser: FC<{
q: string;
handler?: (prev: any, data: any) => any;
}> = ({ q, handler }) => {
const [s] = useSubscription({ query: q }, handler);
context?: Partial<OperationContext>;
}> = ({ q, handler, context }) => {
const [s] = useSubscription({ query: q, context }, handler);
state = s;
return <p>{s.data}</p>;
};
Expand Down Expand Up @@ -70,6 +72,24 @@ describe('on initial useEffect', () => {
});
});

it('should support setting context in useSubscription params', () => {
const context = { url: 'test' };
act(() => {
JoviDeCroock marked this conversation as resolved.
Show resolved Hide resolved
renderer.create(<SubscriptionUser q={query} context={context} />);
});
expect(client.executeSubscription).toBeCalledWith(
{
key: expect.any(Number),
query: expect.any(Object),
variables: {},
},
{
url: 'test',
meta: { source: 'SubscriptionUser' },
}
);
});

describe('on subscription', () => {
it('forwards client response', () => {
const wrapper = renderer.create(<SubscriptionUser q={query} />);
Expand Down
9 changes: 7 additions & 2 deletions src/hooks/useSubscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import { CombinedError, noop } from '../utils';
import { useDevtoolsContext } from './useDevtoolsContext';
import { useRequest } from './useRequest';
import { useImmediateState } from './useImmediateState';
import { OperationContext } from '../types';

export interface UseSubscriptionArgs<V> {
query: DocumentNode | string;
variables?: V;
context?: Partial<OperationContext>;
}

export type SubscriptionHandler<T, R> = (prev: R | undefined, data: T) => R;
Expand Down Expand Up @@ -46,7 +48,10 @@ export const useSubscription = <T = any, R = T, V = object>(
unsubscribe.current();

[unsubscribe.current] = pipe(
client.executeSubscription(request, devtoolsContext),
client.executeSubscription(request, {
...devtoolsContext,
...args.context,
}),
subscribe(({ data, error, extensions }) => {
setState(s => ({
fetching: true,
Expand All @@ -56,7 +61,7 @@ export const useSubscription = <T = any, R = T, V = object>(
}));
})
);
}, [client, devtoolsContext, handler, request, setState]);
}, [client, devtoolsContext, handler, request, setState, args.context]);

// Trigger subscription on query change
// We don't use useImmediateEffect here as we have no way of
Expand Down