-
Notifications
You must be signed in to change notification settings - Fork 4
Open
Description
Problem
STAC catalogs and collections contain links to child resources (child catalogs, collections, or items). Currently, stac-react has no built-in way to:
- Fetch all child resources from a parent
- Navigate catalog hierarchies
- Browse collections within a catalog
- Fetch items linked from a collection
Users must manually parse links and create their own queries, duplicating logic across applications.
Current Behavior
// Must manually parse links and fetch
const { collection } = useCollection('my-collection');
if (collection?.links) {
const childLinks = collection.links.filter(l => l.rel === 'child');
// Now what? Manually fetch each one?
}No built-in way to fetch child resources automatically.
Desired Behavior
import { useStacChildren } from '@developmentseed/stac-react';
function CatalogBrowser({ catalog }) {
const { catalogs, collections, isLoading, error } = useStacChildren(catalog);
return (
<div>
<h2>Child Catalogs</h2>
{catalogs?.map(cat => <CatalogCard key={cat.id} catalog={cat} />)}
<h2>Collections</h2>
{collections?.map(col => <CollectionCard key={col.id} collection={col} />)}
</div>
);
}Use Cases from stac-map
stac-map has useStacChildren that:
const { catalogs, collections } = useStacChildren({
value, // Any STAC resource
enabled: !!value && !collectionsLink,
});
// Automatically:
// 1. Finds all links with rel="child"
// 2. Fetches each child resource
// 3. Separates catalogs from collections
// 4. Returns typed arraysProposed Solution
Hook Signature
type UseStacChildrenOptions = {
/** The parent STAC resource (catalog, collection, or item) */
value?: StacCatalog | StacCollection | StacItem;
/** Enable/disable the queries */
enabled?: boolean;
/** Which link relations to follow (default: ['child', 'item']) */
relations?: string[];
};
type UseStacChildrenResult = {
/** Child catalogs found */
catalogs?: StacCatalog[];
/** Child collections found */
collections?: StacCollection[];
/** Child items found */
items?: StacItem[];
/** All children (mixed types) */
children?: (StacCatalog | StacCollection | StacItem)[];
/** Loading state */
isLoading: boolean;
/** Error state */
error?: Error;
};
function useStacChildren(
options: UseStacChildrenOptions
): UseStacChildrenResult;Implementation
import { useQueries } from '@tanstack/react-query';
import { useMemo } from 'react';
function useStacChildren({
value,
enabled = true,
relations = ['child', 'item'],
}: UseStacChildrenOptions): UseStacChildrenResult {
// Find child links
const childLinks = useMemo(() => {
if (!value?.links) return [];
return value.links.filter(link => relations.includes(link.rel));
}, [value, relations]);
// Fetch all children in parallel
const results = useQueries({
queries: childLinks.map(link => ({
queryKey: ['stac-value', link.href],
queryFn: async () => {
const response = await fetch(link.href, {
headers: { 'Accept': 'application/json' },
});
if (!response.ok) {
throw new Error(`Failed to fetch ${link.href}: ${response.statusText}`);
}
return response.json();
},
enabled: enabled && !!value,
retry: false,
})),
combine: (results) => ({
data: results.map(r => r.data).filter(Boolean),
isLoading: results.some(r => r.isLoading),
error: results.find(r => r.error)?.error,
}),
});
// Separate by type
return useMemo(() => {
const catalogs: StacCatalog[] = [];
const collections: StacCollection[] = [];
const items: StacItem[] = [];
for (const child of results.data) {
switch (child?.type) {
case 'Catalog':
catalogs.push(child);
break;
case 'Collection':
collections.push(child);
break;
case 'Feature':
items.push(child);
break;
}
}
return {
catalogs: catalogs.length > 0 ? catalogs : undefined,
collections: collections.length > 0 ? collections : undefined,
items: items.length > 0 ? items : undefined,
children: results.data,
isLoading: results.isLoading,
error: results.error,
};
}, [results.data, results.isLoading, results.error]);
}Example Usage Patterns
1. Catalog Browser
function CatalogViewer({ catalog }) {
const { catalogs, collections, isLoading } = useStacChildren({
value: catalog
});
if (isLoading) return <Loading />;
return (
<div>
{catalogs && (
<section>
<h2>Subcatalogs ({catalogs.length})</h2>
{catalogs.map(cat => (
<CatalogCard key={cat.id} catalog={cat} />
))}
</section>
)}
{collections && (
<section>
<h2>Collections ({collections.length})</h2>
{collections.map(col => (
<CollectionCard key={col.id} collection={col} />
))}
</section>
)}
</div>
);
}2. Recursive Catalog Tree
function CatalogTree({ catalog, depth = 0 }) {
const { catalogs, collections } = useStacChildren({
value: catalog,
enabled: depth < 3, // Limit depth
});
return (
<div style={{ marginLeft: depth * 20 }}>
<h3>{catalog.title || catalog.id}</h3>
{collections?.map(col => (
<CollectionItem key={col.id} collection={col} />
))}
{catalogs?.map(cat => (
<CatalogTree key={cat.id} catalog={cat} depth={depth + 1} />
))}
</div>
);
}3. Collection Items
function CollectionItemsGrid({ collection }) {
const { items, isLoading } = useStacChildren({
value: collection,
relations: ['item'], // Only fetch items
});
return (
<div className="grid">
{items?.map(item => (
<ItemCard key={item.id} item={item} />
))}
</div>
);
}4. Conditional Loading
function ConditionalChildren({ value, expanded }) {
const { catalogs, collections } = useStacChildren({
value,
enabled: expanded, // Only fetch when expanded
});
// Children only load when user expands the section
}Advanced Features
Custom Link Following
// Follow any custom link relations
const { children } = useStacChildren({
value: catalog,
relations: ['child', 'item', 'derived_from'],
});Progress Tracking
function useStacChildrenWithProgress(value) {
const childLinks = value?.links?.filter(l => l.rel === 'child') || [];
const [completed, setCompleted] = useState(0);
const results = useQueries({
queries: childLinks.map(link => ({
// ... query config
onSuccess: () => setCompleted(c => c + 1),
})),
});
return {
...results,
progress: childLinks.length > 0 ? completed / childLinks.length : 0,
};
}Benefits
- ✅ Automatic child resource fetching
- ✅ Parallel loading for performance
- ✅ Type separation (catalogs vs collections vs items)
- ✅ Enables catalog tree navigation
- ✅ Simplifies recursive browsing
- ✅ Leverages React Query caching
Performance Considerations
- Fetches all children in parallel (not sequential)
- Respects React Query cache (won't refetch)
enabledprop allows lazy loading- Consider pagination for large child sets (future enhancement)
Breaking Changes
None - this is new functionality.
Testing Requirements
- Test with catalogs containing children
- Test with collections containing items
- Test type separation logic
- Test enabled/disabled state
- Test error handling for failed child fetches
- Test with empty/no children
- Test custom relations
- Test parallel loading performance
Documentation Requirements
- Document the hook API
- Provide catalog browser example
- Show recursive tree pattern
- Document performance characteristics
- Show lazy loading patterns
- Document custom link relations
Metadata
Metadata
Assignees
Labels
No labels