dist/plugins/web-data-fetching-graphql-urql/skills/web-data-fetching-graphql-urql/SKILL.md
URQL GraphQL client patterns - useQuery, useMutation, exchange architecture, caching strategies, subscriptions
npx skillsauth add agents-inc/skills web-data-fetching-graphql-urqlInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Quick Guide: Use URQL for GraphQL APIs when you need a lightweight, customizable client with exchange-based architecture. Start minimal with document caching, add normalized caching via Graphcache when needed. Bundle size is ~12KB gzipped (core), ~20KB with Graphcache. Exchange order is critical: synchronous exchanges before asynchronous, fetchExchange always last. v6+ defaults to GET for small queries - set
preferGetMethod: falseif your server only supports POST. Current version: @urql/core v6.0.1 (urql v5.0.1)
<critical_requirements>
(You MUST configure exchange order correctly - synchronous exchanges (cacheExchange) before asynchronous (fetchExchange))
(You MUST include __typename in optimistic responses for Graphcache cache normalization)
(You MUST set preferGetMethod: false if your GraphQL server does NOT support GET requests - v6+ defaults to GET for queries under 2048 characters)
</critical_requirements>
Auto-detection: URQL, urql, useQuery, useMutation, useSubscription, cacheExchange, fetchExchange, Graphcache, exchanges, gql, Client
When to use:
When NOT to use:
Key patterns covered:
Detailed Resources:
URQL follows the principle of progressive enhancement. The core package provides document caching and basic fetching, while advanced features like normalized caching, authentication, and offline support are added through exchanges.
Core Principles:
URQL's Data Flow:
Three Architectural Layers:
Configure the Client with exchanges in the correct order. Sync exchanges (cacheExchange) before async (fetchExchange).
import { Client, cacheExchange, fetchExchange } from "urql";
const client = new Client({
url: GRAPHQL_ENDPOINT,
exchanges: [cacheExchange, fetchExchange],
});
Wrap your app with <Provider value={client}> to enable hooks. See examples/core.md for full setup.
Returns a [result, reexecuteQuery] tuple. Always handle all states: fetching, error, data.
const [result, reexecuteQuery] = useQuery<UsersData, UsersVariables>({
query: USERS_QUERY,
variables: { limit: DEFAULT_PAGE_SIZE },
requestPolicy: "cache-and-network",
});
const { data, fetching, error, stale } = result;
if (fetching && !data) return <Skeleton />; // Initial load only
if (error && !data) return <Error message={error.message} />;
Key: check fetching && !data for initial load vs background refresh. Use pause: !userId for conditional queries. See examples/core.md for full examples.
Returns a [result, executeMutation] tuple. The execute function returns a Promise.
const [result, executeMutation] = useMutation<CreatePostData>(CREATE_POST);
const response = await executeMutation({ input });
if (response.error) {
// Handle error
return;
}
Disable form inputs during result.fetching. See examples/core.md for create/update/delete patterns.
Exchanges are middleware that process operations and results. Order matters critically.
exchanges: [
mapExchange, // 1. Error handling (catches all errors)
cacheExchange, // 2. Sync cache (fast path)
authExchange, // 3. Auth headers
retryExchange, // 4. Retry logic
fetchExchange, // 5. Network (always last)
];
See examples/exchanges.md for auth, retry, Graphcache, and custom exchange patterns.
Upgrade from document cache to normalized cache when you need automatic entity deduplication, optimistic updates, or cache manipulation after mutations.
import { cacheExchange } from "@urql/exchange-graphcache";
cacheExchange({
keys: { Product: (data) => data.sku as string },
updates: {
Mutation: {
createTodo: (result, _args, cache) => {
/* update list */
},
},
},
optimistic: {
toggleTodo: (args) => ({
__typename: "Todo",
id: args.id,
completed: args.completed,
}),
},
});
Always include __typename in optimistic responses. See examples/exchanges.md for full Graphcache configuration.
| Policy | Behavior | Use Case |
| ------------------- | ------------------------------------------------ | ---------------------- |
| cache-first | Return cached if available, else fetch (default) | Most queries |
| cache-only | Only return cached, never fetch | Offline-first |
| network-only | Always fetch, skip cache read | Critical fresh data |
| cache-and-network | Return cached immediately, then fetch and update | Stale-while-revalidate |
Use cache-and-network for best UX in most cases. Force refetch with reexecuteQuery({ requestPolicy: "network-only" }).
Real-time data via WebSocket using subscriptionExchange with graphql-ws.
const [result] = useSubscription<NotificationData>({
query: NOTIFICATION_SUBSCRIPTION,
variables: { userId },
pause: !userId,
});
Subscriptions auto-unsubscribe on unmount. Accumulate events with a reducer and memoized handler. See examples/subscriptions.md for setup and advanced patterns.
URQL wraps all errors in CombinedError, which can contain both networkError and graphQLErrors. GraphQL allows partial data with errors - don't discard useful data.
if (error?.networkError) {
// Network failed entirely - show retry
}
if (error?.graphQLErrors.length) {
// Some fields failed, data may be partial
}
if (data && error) {
// Show partial data with warning banner
}
See examples/core.md for component-level error handling patterns.
</patterns><red_flags>
High Priority Issues:
__typename in optimistic responses - Graphcache normalization fails silentlyMedium Priority Issues:
pause for conditional queries - Unnecessary network requests with undefined variablescache-and-network - Missing stale-while-revalidate UX benefitdata is undefinedGotchas & Edge Cases:
fetching is true during both initial load AND background refresh - check fetching && !data for initial load onlystale indicates cached data is being revalidated - show "updating" indicator, don't show spinnerid or _id by default - configure keys for custom identifierspollInterval is not built-in - use requestPolicyExchange for TTL-based refreshpreferGetMethod: false if server only supports POSTpreferGetMethod: false being ignored (nullish coalescing fix)dedupExchange removed - deduplication is built into the core client (just remove from exchanges array)</red_flags>
<critical_reminders>
(You MUST configure exchange order correctly - synchronous exchanges (cacheExchange) before asynchronous (fetchExchange))
(You MUST include __typename in optimistic responses for Graphcache cache normalization)
(You MUST set preferGetMethod: false if your GraphQL server does NOT support GET requests - v6+ defaults to GET for queries under 2048 characters)
Failure to follow these rules will cause cache corruption, stale data, and production bugs.
</critical_reminders>
development
Material Design component library for Vue 3
development
VitePress 1.x — Vue-powered static site generator for documentation sites, built on Vite
tools
Docusaurus 3.x documentation framework — site configuration, docs/blog plugins, sidebars, versioning, MDX, swizzling, and deployment
development
TanStack Form patterns - useForm, form.Field, validators, arrays, linked fields, createFormHook, type safety