feat(graphiql): add data fetching hooks
WHY are these changes introduced?
This PR adds custom React hooks for server health monitoring and data fetching. This is the sixth PR in the 8-PR migration stack.
Context: The current template-based GraphiQL implementation uses vanilla JavaScript with setInterval to poll the server for health status and app installation state. By creating reusable React hooks, we can:
- Integrate polling with React's component lifecycle
- Automatically clean up intervals when components unmount
- Use React state management for server status
- Make polling logic testable and reusable
- Handle errors gracefully without breaking the UI
WHAT is this pull request doing?
This PR adds two custom hooks that replace vanilla JavaScript polling with React-based data fetching.
Key Changes:
1. usePolling Hook (src/hooks/usePolling.ts):
A generic hook for interval-based polling with proper cleanup and error handling.
Features:
- Calls a callback function at regular intervals
- Supports async functions (returns Promise)
- Handles both sync and async errors gracefully (logs but doesn't crash)
- Uses
useRefto avoid stale closure issues - Calls callback immediately on mount, then at intervals
- Automatically cleans up interval on unmount
- Can be enabled/disabled dynamically
Usage:
usePolling(async () => {
await checkServerHealth()
}, {
interval: 2000, // Poll every 2 seconds
enabled: isActive // Optional: control polling
})
2. useServerStatus Hook (src/hooks/useServerStatus.ts):
Monitors server health and app installation status by polling two endpoints.
Two Polling Mechanisms:
Ping Polling (/graphiql/ping - every 2 seconds by default):
- Checks if dev server is running
- Uses timeout mechanism: marks server dead if no response within 3 seconds
- Clears pending timeouts on successful response
- Updates
serverIsLivestate
Status Polling (/graphiql/status - every 5 seconds by default):
- Checks if app is installed in store
- Fetches store information (
storeFqdn,appName,appUrl) - Updates
appIsInstalledstate and store info
Return Value:
interface ServerStatus {
serverIsLive: boolean // Server is responding to pings
appIsInstalled: boolean // App is installed in store
storeFqdn?: string // e.g., "my-store.myshopify.com"
appName?: string // e.g., "My Test App"
appUrl?: string // e.g., "http://localhost:3000"
}
Usage:
const status = useServerStatus({
baseUrl: 'http://localhost:3457',
pingInterval: 2000, // Optional: default 2000ms
statusInterval: 5000, // Optional: default 5000ms
pingTimeout: 3000, // Optional: default 3000ms
})
// Use status in components
<StatusBadge status={status} />
<ErrorBanner isVisible={!status.serverIsLive} />
<LinkPills status={status} />
Error Handling:
- Network errors are caught and treated as "server down" or "app not installed"
- Polling continues even if individual requests fail
- No errors are thrown to crash the application
- Uses intentional catch-all blocks (with eslint disable comments)
Replaces:
- Vanilla JavaScript
setIntervalpolling logic - Manual timeout tracking and cleanup
- Direct DOM manipulation based on server status
Testing:
- 386 lines of comprehensive tests (176 for usePolling, 210 for useServerStatus)
- Tests interval execution
- Tests error handling
- Tests cleanup on unmount
- Tests timeout behavior
- Tests state updates
Files Added:
src/hooks/usePolling.ts- Generic polling hook (46 lines)src/hooks/usePolling.test.ts- Tests (176 lines)src/hooks/useServerStatus.ts- Server status hook (102 lines)src/hooks/useServerStatus.test.ts- Tests (210 lines)src/hooks/index.ts- Barrel exports
Dependencies
Builds on:
- PR #6578 (package foundation)
- PR #6579 (validation and types)
Used by:
- PR #6580 (StatusBadge, ErrorBanner, LinkPills need
ServerStatustype) - Subsequent PRs will use these hooks in the main layout
How to test your changes?
# Run hook tests
pnpm --filter @shopify/graphiql-console test usePolling.test.ts
pnpm --filter @shopify/graphiql-console test useServerStatus.test.ts
# Type check
pnpm --filter @shopify/graphiql-console tsc --noEmit
# All 386 lines of tests should pass, covering:
# - Polling interval execution
# - Immediate first call on mount
# - Interval cleanup on unmount
# - Error handling (sync and async)
# - Enabled/disabled toggling
# - Server ping timeout behavior
# - Status endpoint response parsing
# - Network error handling
Manual Testing: You can test the hooks in the App component:
import {useServerStatus} from './hooks'
const status = useServerStatus({
baseUrl: 'http://localhost:3457'
})
console.log('Server status:', status)
// Stop the dev server and watch serverIsLive change to false
// Restart it and watch it change back to true
Measuring impact
- [x] n/a - these are foundational data fetching hooks
Checklist
- [x] I've considered possible cross-platform impacts (Mac, Linux, Windows)
- [x] I've considered possible documentation changes
[!WARNING] This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite. Learn more
- #6585

- #6584

- #6583
π (View in Graphite) - #6582

- #6581

- #6580

- #6579

- #6578

main
This stack of pull requests is managed by Graphite. Learn more about stacking.
Coverage report
St.:grey_question: |
Category | Percentage | Covered / Total |
|---|---|---|---|
| π‘ | Statements | 79.36% (+0.02% πΌ) |
13737/17309 |
| π‘ | Branches | 73.27% (-0% π») |
6721/9173 |
| π‘ | Functions | 79.38% (-0.03% π») |
3545/4466 |
| π‘ | Lines | 79.73% (+0.03% πΌ) |
12969/16267 |
Show new covered files π£
St.:grey_question: |
File | Statements | Branches | Functions | Lines |
|---|---|---|---|---|---|
| π’ | ... / usePolling.ts |
100% | 100% | 83.33% | 100% |
| π’ | ... / useServerStatus.ts |
82.35% | 57.14% | 66.67% | 88.46% |
Test suite run success
3437 tests passing in 1399 suites.
Report generated by π§ͺjest coverage report action from eeb9b92fddd558af3c4b125ab14156c27b7af58b