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 arrays
Proposed 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)
enabled prop allows lazy loading
- Consider pagination for large child sets (future enhancement)
Breaking Changes
None - this is new functionality.
Testing Requirements
Documentation Requirements
Problem
STAC catalogs and collections contain links to child resources (child catalogs, collections, or items). Currently, stac-react has no built-in way to:
Users must manually parse links and create their own queries, duplicating logic across applications.
Current Behavior
No built-in way to fetch child resources automatically.
Desired Behavior
Use Cases from stac-map
stac-map has
useStacChildrenthat:Proposed Solution
Hook Signature
Implementation
Example Usage Patterns
1. Catalog Browser
2. Recursive Catalog Tree
3. Collection Items
4. Conditional Loading
Advanced Features
Custom Link Following
Progress Tracking
Benefits
Performance Considerations
enabledprop allows lazy loadingBreaking Changes
None - this is new functionality.
Testing Requirements
Documentation Requirements