urql
Version:
A highly customizable and versatile GraphQL client for React
783 lines (740 loc) • 25.6 kB
JavaScript
Object.defineProperty(exports, '__esModule', { value: true });
var core = require('@urql/core');
var React = require('react');
var wonka = require('wonka');
var OBJ = {};
/** `urql`'s React Context.
*
* @remarks
* The React Context that `urql`’s {@link Client} will be provided with.
* You may use the reexported {@link Provider} to provide a `Client` as well.
*/
var Context = React.createContext(OBJ);
/** Provider for `urql`'s {@link Client} to GraphQL hooks.
*
* @remarks
* `Provider` accepts a {@link Client} and provides it to all GraphQL hooks,
* and {@link useClient}.
*
* You should make sure to create a {@link Client} and provide it with the
* `Provider` to parts of your component tree that use GraphQL hooks.
*
* @example
* ```tsx
* import { Provider } from 'urql';
* // All of `@urql/core` is also re-exported by `urql`:
* import { Client, cacheExchange, fetchExchange } from '@urql/core';
*
* const client = new Client({
* url: 'https://API',
* exchanges: [cacheExchange, fetchExchange],
* });
*
* const App = () => (
* <Provider value={client}>
* <Component />
* </Provider>
* );
* ```
*/
var Provider = Context.Provider;
/** React Consumer component, providing the {@link Client} provided on a parent component.
* @remarks
* This is an alias for {@link Context.Consumer}.
*/
var Consumer = Context.Consumer;
Context.displayName = 'UrqlContext';
/** Hook returning a {@link Client} from {@link Context}.
*
* @remarks
* `useClient` is a convenience hook, which accesses `urql`'s {@link Context}
* and returns the {@link Client} defined on it.
*
* This will be the {@link Client} you passed to a {@link Provider}
* you wrapped your elements containing this hook with.
*
* @throws
* In development, if the component you call `useClient()` in is
* not wrapped in a {@link Provider}, an error is thrown.
*/
var useClient = () => {
var client = React.useContext(Context);
if (client === OBJ && process.env.NODE_ENV !== 'production') {
var error = "No client has been specified using urql's Provider. please create a client and add a Provider.";
console.error(error);
throw new Error(error);
}
return client;
};
var initialState = {
fetching: false,
stale: false,
hasNext: false,
error: undefined,
data: undefined,
extensions: undefined,
operation: undefined
};
// Two operations are considered equal if they have the same key
var areOperationsEqual = (a, b) => {
return a === b || !!(a && b && a.key === b.key);
};
/**
* Checks if two objects are shallowly different with a special case for
* 'operation' where it compares the key if they are not the otherwise equal
*/
var isShallowDifferent = (a, b) => {
for (var key in a) if (!(key in b)) return true;
for (var _key in b) {
if (_key === 'operation' ? !areOperationsEqual(a[_key], b[_key]) : a[_key] !== b[_key]) {
return true;
}
}
return false;
};
var computeNextState = (prevState, result) => {
var newState = {
...prevState,
...result,
data: result.data !== undefined || result.error ? result.data : prevState.data,
fetching: !!result.fetching,
stale: !!result.stale,
error: result.fetching ? prevState.error : result.error
};
return isShallowDifferent(prevState, newState) ? newState : prevState;
};
var hasDepsChanged = (a, b) => {
for (var i = 0, l = b.length; i < l; i++) if (a[i] !== b[i]) return true;
return false;
};
var isDispatching = false;
function deferDispatch(setState, value) {
if (!isDispatching || value === undefined) {
try {
isDispatching = true;
return setState(value);
} finally {
isDispatching = false;
}
} else {
Promise.resolve(value).then(setState);
}
}
/** State of the last mutation executed by your {@link useMutation} hook.
*
* @remarks
* `UseMutationState` is returned (in a tuple) by {@link useMutation} and
* gives you the {@link OperationResult} of the last mutation executed
* with {@link UseMutationExecute}.
*
* Even if the mutation document passed to {@link useMutation} changes,
* the state isn’t reset, so you can keep displaying the previous result.
*/
/** Triggers {@link useMutation} to execute its GraphQL mutation operation.
*
* @param variables - variables using which the mutation will be executed.
* @param context - optionally, context options that will be merged with the hook's
* {@link UseQueryArgs.context} options and the `Client`’s options.
* @returns the {@link OperationResult} of the mutation.
*
* @remarks
* When called, {@link useMutation} will start the GraphQL mutation
* it currently holds and use the `variables` passed to it.
*
* Once the mutation response comes back from the API, its
* returned promise will resolve to the mutation’s {@link OperationResult}
* and the {@link UseMutationState} will be updated with the result.
*
* @example
* ```ts
* const [result, executeMutation] = useMutation(UpdateTodo);
* const start = async ({ id, title }) => {
* const result = await executeMutation({ id, title });
* };
*/
/** Result tuple returned by the {@link useMutation} hook.
*
* @remarks
* Similarly to a `useState` hook’s return value,
* the first element is the {@link useMutation}’s state, updated
* as mutations are executed with the second value, which is
* used to start mutations and is a {@link UseMutationExecute}
* function.
*/
/** Hook to create a GraphQL mutation, run by passing variables to the returned execute function.
*
* @param query - a GraphQL mutation document which `useMutation` will execute.
* @returns a {@link UseMutationResponse} tuple of a {@link UseMutationState} result,
* and an execute function to start the mutation.
*
* @remarks
* `useMutation` allows GraphQL mutations to be defined and keeps its state
* after the mutation is started with the returned execute function.
*
* Given a GraphQL mutation document it returns state to keep track of the
* mutation state and a {@link UseMutationExecute} function, which accepts
* variables for the mutation to be executed.
* Once called, the mutation executes and the state will be updated with
* the mutation’s result.
*
* @see {@link https://urql.dev/goto/urql/docs/basics/react-preact/#mutations} for `useMutation` docs.
*
* @example
* ```ts
* import { gql, useMutation } from 'urql';
*
* const UpdateTodo = gql`
* mutation ($id: ID!, $title: String!) {
* updateTodo(id: $id, title: $title) {
* id, title
* }
* }
* `;
*
* const UpdateTodo = () => {
* const [result, executeMutation] = useMutation(UpdateTodo);
* const start = async ({ id, title }) => {
* const result = await executeMutation({ id, title });
* };
* // ...
* };
* ```
*/
function useMutation(query) {
var isMounted = React.useRef(true);
var client = useClient();
var [state, setState] = React.useState(initialState);
var executeMutation = React.useCallback((variables, context) => {
deferDispatch(setState, {
...initialState,
fetching: true
});
return wonka.toPromise(wonka.take(1)(wonka.filter(result => !result.hasNext)(wonka.onPush(result => {
if (isMounted.current) {
deferDispatch(setState, {
fetching: false,
stale: result.stale,
data: result.data,
error: result.error,
extensions: result.extensions,
operation: result.operation,
hasNext: result.hasNext
});
}
})(client.executeMutation(core.createRequest(query, variables), context || {})))));
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[client, query, setState]);
React.useEffect(() => {
isMounted.current = true;
return () => {
isMounted.current = false;
};
}, []);
return [state, executeMutation];
}
/** Creates a request from a query and variables but preserves reference equality if the key isn't changing
* @internal
*/
function useRequest(query, variables) {
var prev = React.useRef(undefined);
return React.useMemo(() => {
var request = core.createRequest(query, variables);
// We manually ensure reference equality if the key hasn't changed
if (prev.current !== undefined && prev.current.key === request.key) {
return prev.current;
} else {
prev.current = request;
return request;
}
}, [query, variables]);
}
var getCacheForClient = client => {
if (!client._react) {
var reclaim = new Set();
var map = new Map();
if (client.operations$ /* not available in mocks */) {
wonka.subscribe(operation => {
if (operation.kind === 'teardown' && reclaim.has(operation.key)) {
reclaim.delete(operation.key);
map.delete(operation.key);
}
})(client.operations$);
}
client._react = {
get(key) {
return map.get(key);
},
set(key, value) {
reclaim.delete(key);
map.set(key, value);
},
clear(key) {
reclaim.delete(key);
map.delete(key);
},
dispose(key) {
reclaim.add(key);
}
};
}
return client._react;
};
/* eslint-disable react-hooks/exhaustive-deps */
/** Input arguments for the {@link useQuery} hook.
*
* @param query - The GraphQL query that `useQuery` executes.
* @param variables - The variables for the GraphQL query that `useQuery` executes.
*/
/** State of the current query, your {@link useQuery} hook is executing.
*
* @remarks
* `UseQueryState` is returned (in a tuple) by {@link useQuery} and
* gives you the updating {@link OperationResult} of GraphQL queries.
*
* Even when the query and variables passed to {@link useQuery} change,
* this state preserves the prior state and sets the `fetching` flag to
* `true`.
* This allows you to display the previous state, while implementing
* a separate loading indicator separately.
*/
/** Triggers {@link useQuery} to execute a new GraphQL query operation.
*
* @param opts - optionally, context options that will be merged with the hook's
* {@link UseQueryArgs.context} options and the `Client`’s options.
*
* @remarks
* When called, {@link useQuery} will re-execute the GraphQL query operation
* it currently holds, even if {@link UseQueryArgs.pause} is set to `true`.
*
* This is useful for executing a paused query or re-executing a query
* and get a new network result, by passing a new request policy.
*
* ```ts
* const [result, reexecuteQuery] = useQuery({ query });
*
* const refresh = () => {
* // Re-execute the query with a network-only policy, skipping the cache
* reexecuteQuery({ requestPolicy: 'network-only' });
* };
* ```
*/
/** Result tuple returned by the {@link useQuery} hook.
*
* @remarks
* Similarly to a `useState` hook’s return value,
* the first element is the {@link useQuery}’s result and state,
* a {@link UseQueryState} object,
* and the second is used to imperatively re-execute the query
* via a {@link UseQueryExecute} function.
*/
var isSuspense = (client, context) => context && context.suspense !== undefined ? !!context.suspense : client.suspense;
/** Hook to run a GraphQL query and get updated GraphQL results.
*
* @param args - a {@link UseQueryArgs} object, to pass a `query`, `variables`, and options.
* @returns a {@link UseQueryResponse} tuple of a {@link UseQueryState} result, and re-execute function.
*
* @remarks
* `useQuery` allows GraphQL queries to be defined and executed.
* Given {@link UseQueryArgs.query}, it executes the GraphQL query with the
* context’s {@link Client}.
*
* The returned result updates when the `Client` has new results
* for the query, and changes when your input `args` change.
*
* Additionally, if the `suspense` option is enabled on the `Client`,
* the `useQuery` hook will suspend instead of indicating that it’s
* waiting for a result via {@link UseQueryState.fetching}.
*
* @see {@link https://urql.dev/goto/urql/docs/basics/react-preact/#queries} for `useQuery` docs.
*
* @example
* ```ts
* import { gql, useQuery } from 'urql';
*
* const TodosQuery = gql`
* query { todos { id, title } }
* `;
*
* const Todos = () => {
* const [result, reexecuteQuery] = useQuery({
* query: TodosQuery,
* variables: {},
* });
* // ...
* };
* ```
*/
function useQuery(args) {
var client = useClient();
var cache = getCacheForClient(client);
var suspense = isSuspense(client, args.context);
var request = useRequest(args.query, args.variables);
var source = React.useMemo(() => {
if (args.pause) return null;
var source = client.executeQuery(request, {
requestPolicy: args.requestPolicy,
...args.context
});
return suspense ? wonka.onPush(result => {
cache.set(request.key, result);
})(source) : source;
}, [cache, client, request, suspense, args.pause, args.requestPolicy, args.context]);
var getSnapshot = React.useCallback((source, suspense) => {
if (!source) return {
fetching: false
};
var result = cache.get(request.key);
if (!result) {
var resolve;
var subscription = wonka.subscribe(_result => {
result = _result;
if (resolve) resolve(result);
})(wonka.takeWhile(() => suspense && !resolve || !result || 'hasNext' in result && result.hasNext)(source));
if (result == null && suspense) {
var promise = new Promise(_resolve => {
resolve = _resolve;
});
cache.set(request.key, promise);
throw promise;
} else {
subscription.unsubscribe();
}
} else if (suspense && result != null && 'then' in result) {
throw result;
}
if (suspense && result != null && !('then' in result) && result.error) {
var errorResult = result;
queueMicrotask(() => {
if (cache.get(request.key) === errorResult) {
cache.clear(request.key);
}
});
}
return result || {
fetching: true
};
}, [cache, request]);
var deps = [client, request, args.requestPolicy, args.context, args.pause];
var [state, setState] = React.useState(() => [source, computeNextState(initialState, deferDispatch(() => getSnapshot(source, suspense))), deps]);
var currentResult = state[1];
if (source !== state[0] && hasDepsChanged(state[2], deps)) {
setState([source, currentResult = computeNextState(state[1], deferDispatch(() => getSnapshot(source, suspense))), deps]);
}
React.useEffect(() => {
var source = state[0];
var request = state[2][1];
var hasResult = false;
var updateResult = result => {
hasResult = true;
deferDispatch(setState, state => {
var nextResult = computeNextState(state[1], result);
return state[1] !== nextResult ? [state[0], nextResult, state[2]] : state;
});
};
if (source) {
var subscription = wonka.subscribe(updateResult)(wonka.onEnd(() => {
updateResult({
fetching: false
});
})(source));
if (!hasResult) updateResult({
fetching: true
});
return () => {
cache.dispose(request.key);
subscription.unsubscribe();
};
} else {
updateResult({
fetching: false
});
}
}, [cache, state[0], state[2][1]]);
var executeQuery = React.useCallback(opts => {
var context = {
requestPolicy: args.requestPolicy,
...args.context,
...opts
};
deferDispatch(setState, state => {
var source = suspense ? wonka.onPush(result => {
cache.set(request.key, result);
})(client.executeQuery(request, context)) : client.executeQuery(request, context);
return [source, state[1], deps];
});
}, [client, cache, request, suspense, args.requestPolicy, args.context, args.pause]);
return [currentResult, executeQuery];
}
/* eslint-disable react-hooks/exhaustive-deps */
/** Input arguments for the {@link useSubscription} hook.
*
* @param query - The GraphQL subscription document that `useSubscription` executes.
* @param variables - The variables for the GraphQL subscription that `useSubscription` executes.
*/
/** Combines previous data with an incoming subscription result’s data.
*
* @remarks
* A `SubscriptionHandler` may be passed to {@link useSubscription} to
* aggregate subscription results into a combined {@link UseSubscriptionState.data}
* value.
*
* This is useful when a subscription event delivers a single item, while
* you’d like to display a list of events.
*
* @example
* ```ts
* const NotificationsSubscription = gql`
* subscription { newNotification { id, text } }
* `;
*
* const combineNotifications = (notifications = [], data) => {
* return [...notifications, data.newNotification];
* };
*
* const [result, executeSubscription] = useSubscription(
* { query: NotificationsSubscription },
* combineNotifications,
* );
* ```
*/
/** State of the current subscription, your {@link useSubscription} hook is executing.
*
* @remarks
* `UseSubscriptionState` is returned (in a tuple) by {@link useSubscription} and
* gives you the updating {@link OperationResult} of GraphQL subscriptions.
*
* If a {@link SubscriptionHandler} has been passed to `useSubscription` then
* {@link UseSubscriptionState.data} is instead the updated data as returned
* by the handler, otherwise it’s the latest result’s data.
*
* Hint: Even when the query and variables passed to {@link useSubscription} change,
* this state preserves the prior state.
*/
/** Triggers {@link useSubscription} to reexecute a GraphQL subscription operation.
*
* @param opts - optionally, context options that will be merged with the hook's
* {@link UseSubscriptionArgs.context} options and the `Client`’s options.
*
* @remarks
* When called, {@link useSubscription} will restart the GraphQL subscription
* operation it currently holds. If {@link UseSubscriptionArgs.pause} is set
* to `true`, it will start executing the subscription.
*
* ```ts
* const [result, executeSubscription] = useSubscription({
* query,
* pause: true,
* });
*
* const start = () => {
* executeSubscription();
* };
* ```
*/
/** Result tuple returned by the {@link useSubscription} hook.
*
* @remarks
* Similarly to a `useState` hook’s return value,
* the first element is the {@link useSubscription}’s state,
* a {@link UseSubscriptionState} object,
* and the second is used to imperatively re-execute or start the subscription
* via a {@link UseMutationExecute} function.
*/
/** Hook to run a GraphQL subscription and get updated GraphQL results.
*
* @param args - a {@link UseSubscriptionArgs} object, to pass a `query`, `variables`, and options.
* @param handler - optionally, a {@link SubscriptionHandler} function to combine multiple subscription results.
* @returns a {@link UseSubscriptionResponse} tuple of a {@link UseSubscriptionState} result, and an execute function.
*
* @remarks
* `useSubscription` allows GraphQL subscriptions to be defined and executed.
* Given {@link UseSubscriptionArgs.query}, it executes the GraphQL subscription with the
* context’s {@link Client}.
*
* The returned result updates when the `Client` has new results
* for the subscription, and `data` is updated with the result’s data
* or with the `data` that a `handler` returns.
*
* @example
* ```ts
* import { gql, useSubscription } from 'urql';
*
* const NotificationsSubscription = gql`
* subscription { newNotification { id, text } }
* `;
*
* const combineNotifications = (notifications = [], data) => {
* return [...notifications, data.newNotification];
* };
*
* const Notifications = () => {
* const [result, executeSubscription] = useSubscription(
* { query: NotificationsSubscription },
* combineNotifications,
* );
* // ...
* };
* ```
*/
function useSubscription(args, handler) {
var client = useClient();
var request = useRequest(args.query, args.variables);
var handlerRef = React.useRef(handler);
handlerRef.current = handler;
var source = React.useMemo(() => !args.pause ? client.executeSubscription(request, args.context) : null, [client, request, args.pause, args.context]);
var deps = [client, request, args.context, args.pause];
var [state, setState] = React.useState(() => [source, {
...initialState,
fetching: !!source
}, deps]);
var currentResult = state[1];
if (source !== state[0] && hasDepsChanged(state[2], deps)) {
setState([source, currentResult = computeNextState(state[1], {
fetching: !!source
}), deps]);
}
React.useEffect(() => {
var updateResult = result => {
deferDispatch(setState, state => {
var nextResult = computeNextState(state[1], result);
if (state[1] === nextResult) return state;
if (handlerRef.current && nextResult.data != null && state[1].data !== nextResult.data) {
nextResult.data = handlerRef.current(state[1].data, nextResult.data);
}
return [state[0], nextResult, state[2]];
});
};
if (state[0]) {
return wonka.subscribe(updateResult)(wonka.onEnd(() => {
updateResult({
fetching: !!source
});
})(state[0])).unsubscribe;
} else {
updateResult({
fetching: false
});
}
}, [state[0]]);
// This is the imperative execute function passed to the user
var executeSubscription = React.useCallback(opts => {
var source = client.executeSubscription(request, {
...args.context,
...opts
});
deferDispatch(setState, state => [source, state[1], deps]);
}, [client, request, args.context, args.pause]);
return [currentResult, executeSubscription];
}
/** Props accepted by {@link Mutation}.
*
* @remarks
* `MutationProps` are the props accepted by the {@link Mutation} component.
*
* The result, the {@link MutationState} object, will be passed to
* a {@link MutationProps.children} function, passed as children
* to the `Mutation` component.
*/
/** Object that {@link MutationProps.children} is called with.
*
* @remarks
* This is an extented {@link UseMutationstate} with an added
* {@link MutationState.executeMutation} method, which is usually
* part of a tuple returned by {@link useMutation}.
*/
/** Component Wrapper around {@link useMutation} to run a GraphQL query.
*
* @remarks
* `Mutation` is a component wrapper around the {@link useMutation} hook
* that calls the {@link MutationProps.children} prop, as a function,
* with the {@link MutationState} object.
*/
function Mutation(props) {
var mutation = useMutation(props.query);
return props.children({
...mutation[0],
executeMutation: mutation[1]
});
}
/** Props accepted by {@link Query}.
*
* @remarks
* `QueryProps` are the props accepted by the {@link Query} component,
* which is identical to {@link UseQueryArgs}.
*
* The result, the {@link QueryState} object, will be passed to
* a {@link QueryProps.children} function, passed as children
* to the `Query` component.
*/
/** Object that {@link QueryProps.children} is called with.
*
* @remarks
* This is an extented {@link UseQueryState} with an added
* {@link QueryState.executeQuery} method, which is usually
* part of a tuple returned by {@link useQuery}.
*/
/** Component Wrapper around {@link useQuery} to run a GraphQL query.
*
* @remarks
* `Query` is a component wrapper around the {@link useQuery} hook
* that calls the {@link QueryProps.children} prop, as a function,
* with the {@link QueryState} object.
*/
function Query(props) {
var query = useQuery(props);
return props.children({
...query[0],
executeQuery: query[1]
});
}
/** Props accepted by {@link Subscription}.
*
* @remarks
* `SubscriptionProps` are the props accepted by the {@link Subscription} component,
* which is identical to {@link UseSubscriptionArgs} with an added
* {@link SubscriptionProps.handler} prop, which {@link useSubscription} usually
* accepts as an additional argument.
*
* The result, the {@link SubscriptionState} object, will be passed to
* a {@link SubscriptionProps.children} function, passed as children
* to the `Subscription` component.
*/
/** Object that {@link SubscriptionProps.children} is called with.
*
* @remarks
* This is an extented {@link UseSubscriptionState} with an added
* {@link SubscriptionState.executeSubscription} method, which is usually
* part of a tuple returned by {@link useSubscription}.
*/
/** Component Wrapper around {@link useSubscription} to run a GraphQL subscription.
*
* @remarks
* `Subscription` is a component wrapper around the {@link useSubscription} hook
* that calls the {@link SubscriptionProps.children} prop, as a function,
* with the {@link SubscriptionState} object.
*/
function Subscription(props) {
var subscription = useSubscription(props, props.handler);
return props.children({
...subscription[0],
executeSubscription: subscription[1]
});
}
exports.Consumer = Consumer;
exports.Context = Context;
exports.Mutation = Mutation;
exports.Provider = Provider;
exports.Query = Query;
exports.Subscription = Subscription;
exports.useClient = useClient;
exports.useMutation = useMutation;
exports.useQuery = useQuery;
exports.useSubscription = useSubscription;
Object.keys(core).forEach(function (k) {
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
enumerable: true,
get: function () { return core[k]; }
});
});
//# sourceMappingURL=urql.js.map