feat(web): typed api client + headless hooks (dashboard foundation)#201
Open
merencia wants to merge 2 commits into
Open
feat(web): typed api client + headless hooks (dashboard foundation)#201merencia wants to merge 2 commits into
merencia wants to merge 2 commits into
Conversation
6614c8a to
dccffbb
Compare
dccffbb to
b509f1f
Compare
Comment on lines
+25
to
+67
| /** | ||
| * Minimal data-fetching primitive: runs `fetcher` on mount, whenever `deps` change, and on | ||
| * `refetch()`. A monotonic request id guards against races so a slow, stale response can | ||
| * never overwrite a newer one. No caching or dedup, by design; the API surface is small. | ||
| */ | ||
| export function useApiQuery<T>(fetcher: () => Promise<T>, deps: unknown[], options?: UseApiQueryOptions): QueryState<T> { | ||
| const [state, setState] = useState<{ data?: T; error?: Error; isLoading: boolean }>({ isLoading: true }); | ||
|
|
||
| // Keep the latest fetcher in a ref (updated in an effect, never during render) so the run | ||
| // callback can stay stable without capturing a stale closure. | ||
| const fetcherRef = useRef(fetcher); | ||
| useEffect(() => { | ||
| fetcherRef.current = fetcher; | ||
| }); | ||
|
|
||
| // Monotonic id: only the most recent request may write state. | ||
| const requestId = useRef(0); | ||
|
|
||
| const run = useCallback(() => { | ||
| const id = ++requestId.current; | ||
| setState((prev) => ({ ...prev, isLoading: true, error: undefined })); | ||
| fetcherRef.current().then( | ||
| (data) => { | ||
| if (id === requestId.current) setState({ data, isLoading: false }); | ||
| }, | ||
| (error: unknown) => { | ||
| if (id === requestId.current) setState({ error: error as Error, isLoading: false }); | ||
| }, | ||
| ); | ||
| }, []); | ||
|
|
||
| useEffect(() => { | ||
| run(); | ||
| if (options?.refetchInterval) { | ||
| const timer = setInterval(run, options.refetchInterval); | ||
| return () => clearInterval(timer); | ||
| } | ||
| // deps are the caller's cache key; run/refetchInterval are stable enough to omit. | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, deps); | ||
|
|
||
| return { data: state.data, error: state.error, isLoading: state.isLoading, refetch: run }; | ||
| } |
Contributor
There was a problem hiding this comment.
Nah, let's use Tanstack Query
…ation) the data layer the react dashboard and the pro consume: a typed hono client (hc<ApiApp>, relative paths) plus headless hooks over it (logic separated from presentation, the pro's anti-fork seam). - createApiClient + ApiClientProvider/useApiClient: the client is injected via context, so the pro passes its superset client and the façade a base-path client. - useApiQuery: minimal fetch primitive with loading/error, refetch, polling and a stale-response race guard. no react-query. - domain hooks: useJobs/useJob/useJobsMeta/useJobActions, useQueues/useQueueActions, useOverview/useOverviewTimeseries. source only for now, not built or exported; the ./dashboard subpath + build land with the app. 20 hook tests in jsdom (tdd); vitest/eslint/tsconfig wired to treat src/dashboard as browser code.
Replace the hand-rolled useApiQuery primitive with TanStack Query
(useQuery/useMutation) over the typed hono client, per the plan-mode
decision for the dashboard v2 data layer.
- query hooks keep the { data, error, isLoading, refetch } shape, so
consumers do not change; query keys are structural (no JSON.stringify)
- action hooks keep { run, cancel, rerun } / { toggle } but now invalidate
their queries on success, dropping the manual refetch the callers did
- refetchInterval option preserves the existing poll
- adds @tanstack/react-query; test harness wraps a QueryClientProvider
Addresses review on use-api-query.ts (Giovani: use TanStack Query).
ce13b43 to
13d0393
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
stacked on #200. the data foundation for the react dashboard: a typed hono client and headless hooks (logic split from presentation, the pro anti-fork seam).
createApiClient(hc<ApiApp>, relative paths) +useApiClientvia context, so the pro/façade inject their own clientuseApiQueryprimitive: loading/error, refetch, polling, stale-response race guard (no react-query)./dashboardsubpath + build come with the app