Skip to content

Commit 879bf33

Browse files
Merge pull request #42 from developmentseed/feature/error-handling
Improve error handling
2 parents 3f9be54 + 58eb5dc commit 879bf33

22 files changed

+181
-239
lines changed

.github/workflows/checks.yml

Lines changed: 40 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,63 @@
11
name: Checks
2+
23
on:
3-
push:
4-
branches:
5-
- main
64
pull_request:
7-
branches:
8-
- main
5+
types:
6+
- opened
7+
- synchronize
8+
- reopened
9+
- ready_for_review
10+
11+
concurrency:
12+
group: ${{ github.workflow }}-${{ github.ref }}
13+
cancel-in-progress: true
914

1015
jobs:
1116
prep:
1217
if: github.event.pull_request.draft == false
1318
runs-on: ubuntu-latest
1419

1520
steps:
16-
- name: Cancel Previous Runs
17-
uses: styfle/cancel-workflow-action@0.8.0
18-
with:
19-
access_token: ${{ github.token }}
20-
2121
- name: Checkout
22-
uses: actions/checkout@v2
23-
24-
- name: Read .nvmrc
25-
run: echo "NVMRC=$(cat .nvmrc)" >> $GITHUB_ENV
22+
uses: actions/checkout@v6
2623

27-
- name: Use Node.js ${{ env.NVMRC }}
28-
uses: actions/setup-node@v1
24+
- name: Use Node.js
25+
uses: actions/setup-node@v6
2926
with:
30-
node-version: ${{ env.NVMRC }}
27+
node-version-file: '.nvmrc'
3128

3229
- name: Cache node_modules
33-
uses: actions/cache@v2
30+
uses: actions/cache@v5
3431
id: cache-node-modules
3532
with:
3633
path: node_modules
37-
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }}
34+
key: ${{ runner.os }}-build-${{ hashFiles('**/package.json') }}
3835

3936
- name: Install
40-
run: yarn install
37+
run: corepack enable && yarn install
4138

4239
lint:
4340
needs: prep
4441
runs-on: ubuntu-latest
4542

4643
steps:
4744
- name: Checkout
48-
uses: actions/checkout@v2
45+
uses: actions/checkout@v6
4946

50-
- name: Read .nvmrc
51-
run: echo "NVMRC=$(cat .nvmrc)" >> $GITHUB_ENV
52-
53-
- name: Use Node.js ${{ env.NVMRC }}
54-
uses: actions/setup-node@v1
47+
- name: Use Node.js
48+
uses: actions/setup-node@v6
5549
with:
56-
node-version: ${{ env.NVMRC }}
50+
node-version-file: '.nvmrc'
5751

5852
- name: Cache node_modules
59-
uses: actions/cache@v2
53+
uses: actions/cache@v5
6054
id: cache-node-modules
6155
with:
6256
path: node_modules
63-
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }}
57+
key: ${{ runner.os }}-build-${{ hashFiles('**/package.json') }}
6458

6559
- name: Install
66-
run: yarn install
60+
run: corepack enable && yarn install
6761

6862
- name: Lint
6963
run: yarn lint
@@ -74,54 +68,48 @@ jobs:
7468

7569
steps:
7670
- name: Checkout
77-
uses: actions/checkout@v2
78-
79-
- name: Read .nvmrc
80-
run: echo "NVMRC=$(cat .nvmrc)" >> $GITHUB_ENV
71+
uses: actions/checkout@v6
8172

82-
- name: Use Node.js ${{ env.NVMRC }}
83-
uses: actions/setup-node@v1
73+
- name: Use Node.js
74+
uses: actions/setup-node@v6
8475
with:
85-
node-version: ${{ env.NVMRC }}
76+
node-version-file: '.nvmrc'
8677

8778
- name: Cache node_modules
88-
uses: actions/cache@v2
79+
uses: actions/cache@v5
8980
id: cache-node-modules
9081
with:
9182
path: node_modules
92-
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }}
83+
key: ${{ runner.os }}-build-${{ hashFiles('**/package.json') }}
9384

9485
- name: Install
95-
run: yarn install
86+
run: corepack enable && yarn install
9687

9788
- name: Test
9889
run: yarn test
9990

10091
build:
101-
needs: test
92+
needs: prep
10293
runs-on: ubuntu-latest
10394

10495
steps:
10596
- name: Checkout
106-
uses: actions/checkout@v2
107-
108-
- name: Read .nvmrc
109-
run: echo "NVMRC=$(cat .nvmrc)" >> $GITHUB_ENV
97+
uses: actions/checkout@v6
11098

111-
- name: Use Node.js ${{ env.NVMRC }}
112-
uses: actions/setup-node@v1
99+
- name: Use Node.js
100+
uses: actions/setup-node@v6
113101
with:
114-
node-version: ${{ env.NVMRC }}
102+
node-version-file: '.nvmrc'
115103

116104
- name: Cache node_modules
117-
uses: actions/cache@v2
105+
uses: actions/cache@v5
118106
id: cache-node-modules
119107
with:
120108
path: node_modules
121-
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }}
109+
key: ${{ runner.os }}-build-${{ hashFiles('**/package.json') }}
122110

123111
- name: Install
124-
run: yarn install
112+
run: corepack enable && yarn install
125113

126114
- name: Test
127-
run: yarn build
115+
run: yarn test

docs/MIGRATION.md

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,6 @@ if (error && error.status === 404) {
157157
**After:**
158158

159159
```typescript
160-
import type { ApiErrorType } from 'stac-react';
161-
162160
const { error } = useCollection('my-collection');
163161

164162
if (error?.status === 404) {
@@ -176,13 +174,11 @@ if (error?.status === 404) {
176174
**What's different?**
177175

178176
- Errors are now proper class instances with `ApiError`
179-
- Better TypeScript support with `ApiErrorType`
180177
- Includes HTTP status codes and response details
181178
- Consistent across all hooks
182179

183180
**Migration checklist:**
184181

185-
- [ ] Import `ApiErrorType` from 'stac-react'
186182
- [ ] Update error checks to use typed properties
187183
- [ ] Test error scenarios (404, 500, network failures)
188184

@@ -196,7 +192,7 @@ The most significant changes are in `useStacSearch`:
196192

197193
#### Before: Manual State Management
198194

199-
```typescript
195+
```tsx
200196
function MySearch() {
201197
const {
202198
state,
@@ -226,7 +222,7 @@ function MySearch() {
226222

227223
#### After: TanStack Query Integration
228224

229-
```typescript
225+
```tsx
230226
function MySearch() {
231227
const {
232228
isLoading,
@@ -347,7 +343,7 @@ You now must configure TanStack Query. The library doesn't do this for you (to a
347343

348344
#### Before
349345

350-
```jsx
346+
```tsx
351347
import { StacApiProvider } from 'stac-react';
352348

353349
function App() {
@@ -357,7 +353,7 @@ function App() {
357353

358354
#### After
359355

360-
```jsx
356+
```tsx
361357
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
362358
import { StacApiProvider } from 'stac-react';
363359

@@ -426,7 +422,7 @@ const { collection: col2 } = useCollection('landsat-8');
426422

427423
### Request Deduplication
428424

429-
```typescript
425+
```tsx
430426
// Multiple components request same collection
431427
// Only ONE network request is made, even if 5 components use the hook
432428

@@ -438,7 +434,7 @@ const { collection: col2 } = useCollection('landsat-8');
438434

439435
### Invalidation on API Change
440436

441-
```typescript
437+
```tsx
442438
// If API URL changes, all queries are automatically invalidated
443439
<StacApiProvider apiUrl="https://old-api.com" />
444440
// Switch to new API:
@@ -469,7 +465,7 @@ test('loads collections', async () => {
469465

470466
### After
471467

472-
```typescript
468+
```tsx
473469
import { renderHook, waitFor } from '@testing-library/react';
474470
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
475471
import { useCollections } from 'stac-react';
@@ -518,13 +514,13 @@ If you're using TypeScript, new types are available:
518514

519515
```typescript
520516
import type {
521-
ApiErrorType, // Error response
522517
FetchRequest, // Request types for useStacSearch
523518
SearchRequestPayload, // Search parameters
524519
} from 'stac-react';
525520

526521
// Your hook usage
527-
const { error }: { error?: ApiErrorType } = useCollection('id');
522+
// Error is of type ApiError
523+
const { error } = useCollection('id');
528524

529525
if (error) {
530526
console.log(error.status); // number
@@ -541,15 +537,15 @@ if (error) {
541537

542538
**Before:**
543539

544-
```typescript
540+
```tsx
545541
{state === 'LOADING' && <Skeleton />}
546542
{state === 'IDLE' && results && <Results items={results.features} />}
547543
{error && <Error message={error.message} />}
548544
```
549545

550546
**After:**
551547

552-
```typescript
548+
```tsx
553549
{isLoading && <Skeleton />}
554550
{!isLoading && results && <Results items={results.features} />}
555551
{error && <Error message={error.statusText} />}
@@ -559,15 +555,15 @@ if (error) {
559555

560556
**Before:**
561557

562-
```typescript
558+
```tsx
563559
<button onClick={search} disabled={state === 'LOADING'}>
564560
Search
565561
</button>
566562
```
567563

568564
**After:**
569565

570-
```typescript
566+
```tsx
571567
<button onClick={search} disabled={isLoading || isFetching}>
572568
{isFetching ? 'Searching...' : 'Search'}
573569
</button>
@@ -678,7 +674,6 @@ const testClient = new QueryClient({
678674
- [ ] Replace `state` with `isLoading`/`isFetching` in all components
679675
- [ ] Rename `reload` to `refetch` in all components
680676
- [ ] Replace context data access with individual hooks
681-
- [ ] Update error handling to use typed `ApiErrorType`
682677
- [ ] Update tests to use test QueryClient
683678
- [ ] Remove context data subscriptions
684679
- [ ] Review caching strategy for your app

example/src/App.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ function App() {
2727

2828
// Debug: Verify QueryClient configuration
2929
if (isDevelopment && typeof window !== 'undefined') {
30+
// eslint-disable-next-line no-console
3031
console.log('[App] QueryClient defaults:', queryClient.getDefaultOptions());
3132
}
3233

example/src/pages/Main/ItemDetails.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import { H2 } from '../../components/headers';
44
import Panel from '../../layout/Panel';
55
import { Button } from '../../components/buttons';
66

7+
// eslint-disable-next-line react/prop-types
78
function ItemDetails({ item, onClose }) {
9+
// eslint-disable-next-line react/prop-types
810
const itemUrl = item.links.find((r) => r.rel === 'self')?.href;
911
const { item: newItem, isLoading, error, reload } = useItem(itemUrl);
1012

jest.utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export function makeMockResponse(responseData: string, url: string, init?: ResponseInit): Response {
2+
const response = new Response(responseData, init);
3+
Object.defineProperty(response, 'url', { value: url });
4+
return response;
5+
}

src/context/StacApiProvider.test.tsx

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,13 @@ import { render, screen, waitFor } from '@testing-library/react';
33
import { QueryClient, QueryClientProvider, useQueryClient } from '@tanstack/react-query';
44
import { StacApiProvider } from './index';
55
import { useStacApiContext } from './useStacApiContext';
6+
import { makeMockResponse } from '../../jest.utils';
67

78
// Mock fetch for testing - returns a successful response
89
beforeEach(() => {
9-
(global.fetch as jest.Mock) = jest.fn((url: string) =>
10-
Promise.resolve({
11-
ok: true,
12-
url, // Return the requested URL
13-
json: () =>
14-
Promise.resolve({
15-
links: [],
16-
}),
17-
})
18-
);
10+
(global.fetch as jest.Mock) = jest.fn((url: string) => {
11+
return Promise.resolve(makeMockResponse(JSON.stringify({ links: [] }), url));
12+
});
1913
});
2014

2115
// Component to test that hooks work inside StacApiProvider

0 commit comments

Comments
 (0)