Add Flexible Search Hook
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 30/100
- Issue type
- Feature
- Clarity
- Mostly clear
- Activity status
- Stale
- Tech stack
- react, typescript
Research direction
Start by reading the existing hooks/useStacSearch implementation and its public export, then review related issues #5 and #10 before deciding how the new hook fits. Done means the declarative and stateful hooks coexist, the requested parameter, context, link, pagination, error, and enabled-state tests pass, and the usage and migration documentation is added.
Written by the indexing model from the issue text.
Description
Problem
The current useStacSearch hook is stateful with individual setters for each parameter:
const {
setBbox,
setCollections,
setDateRangeFrom,
setDateRangeTo,
setLimit,
setSortby,
submit,
results,
} = useStacSearch();
This pattern:
- Requires multiple calls to set up a search
- Doesn't work well with declarative React patterns
- Makes it difficult to sync with URL parameters
- Doesn't support passing complete search objects
- Requires explicit
submit()call
Many applications need a declarative search hook where search parameters are props that automatically trigger searches when they change.
Current Behavior
function SearchPage() {
const {
setBbox,
setCollections,
submit,
results
} = useStacSearch();
// Multi-step setup
useEffect(() => {
setBbox([-180, -90, 180, 90]);
setCollections(['collection-1']);
submit(); // Must explicitly submit
}, []);
return <Results data={results} />;
}
Desired Behavior
function SearchPage({ bbox, collections, datetime }) {
// Declarative - automatically searches when params change
const { results, isLoading, error } = useStacSearch({
bbox,
collections,
datetime,
limit: 10,
});
return <Results data={results} />;
}
Use Cases from stac-map
stac-map uses a declarative pattern:
// Search parameters and link are passed directly
const searchQuery = useStacSearch(
{ collections, bbox, datetime }, // Search params
searchLink // Link from STAC API
);
// Automatically re-searches when params change
// Returns useInfiniteQuery for pagination
Proposed Solution
New Declarative Hook
Add a new declarative variant alongside the existing stateful one:
type StacSearchParams = {
ids?: string[];
bbox?: Bbox;
collections?: string[];
datetime?: string;
limit?: number;
sortby?: Sortby[];
query?: Record<string, any>; // CQL2 queries
};
type UseStacSearchDeclarativeOptions = {
/** Search parameters */
params: StacSearchParams;
/** Optional: specific search link to use */
searchLink?: Link;
/** Enable/disable search */
enabled?: boolean;
/** Custom headers */
headers?: Record<string, string>;
};
type UseStacSearchDeclarativeResult = {
/** Search results */
results?: SearchResponse;
/** Loading state */
isLoading: boolean;
isFetching: boolean;
/** Error state */
error?: ApiErrorType;
/** Refetch with same params */
refetch: () => Promise<void>;
/** Pagination (if link-based pagination) */
nextPage?: () => void;
previousPage?: () => void;
hasNextPage?: boolean;
hasPreviousPage?: boolean;
};
function useStacSearchDeclarative(
options: UseStacSearchDeclarativeOptions
): UseStacSearchDeclarativeResult;
Implementation
import { useQuery } from '@tanstack/react-query';
import { useStacApiContext } from '../context/useStacApiContext';
function useStacSearchDeclarative({
params,
searchLink,
enabled = true,
headers = {},
}: UseStacSearchDeclarativeOptions): UseStacSearchDeclarativeResult {
const { stacApi } = useStacApiContext();
const { data, error, isLoading, isFetching, refetch } = useQuery({
queryKey: ['stac-search-declarative', params, searchLink?.href],
queryFn: async () => {
if (searchLink) {
// Use provided search link
return fetchViaLink(searchLink, params);
} else if (stacApi) {
// Use StacApi instance
const response = await stacApi.search({
...params,
dateRange: params.datetime ? parseDateTime(params.datetime) : undefined,
}, headers);
if (!response.ok) {
throw new ApiError(
response.statusText,
response.status,
await response.text(),
response.url
);
}
return response.json();
} else {
throw new Error('Either provide stacApi context or searchLink');
}
},
enabled: enabled && (!!stacApi || !!searchLink),
retry: false,
});
// Extract pagination links
const nextLink = data?.links?.find(l => l.rel === 'next');
const prevLink = data?.links?.find(l => ['prev', 'previous'].includes(l.rel));
return {
results: data,
isLoading,
isFetching,
error,
refetch,
hasNextPage: !!nextLink,
hasPreviousPage: !!prevLink,
};
}
async function fetchViaLink(link: Link, params: StacSearchParams) {
const url = new URL(link.href);
if (link.method === 'POST' || link.body) {
// POST request
return fetch(url.toString(), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...link.headers,
},
body: JSON.stringify({ ...link.body, ...params }),
}).then(r => r.json());
} else {
// GET request
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
url.searchParams.set(
key,
Array.isArray(value) ? value.join(',') : String(value)
);
}
});
return fetch(url.toString()).then(r => r.json());
}
}
Example Usage Patterns
1. Simple Declarative Search
function SimpleSearch() {
const [collections, setCollections] = useState(['landsat-8']);
const [bbox, setBbox] = useState<Bbox>();
const { results, isLoading } = useStacSearchDeclarative({
params: { collections, bbox, limit: 50 },
});
// Automatically re-searches when collections or bbox change
return (
<div>
<CollectionSelector value={collections} onChange={setCollections} />
<BboxSelector value={bbox} onChange={setBbox} />
{isLoading ? <Spinner /> : <Results items={results?.features} />}
</div>
);
}
2. URL-Synced Search
function UrlSyncedSearch() {
const [searchParams, setSearchParams] = useSearchParams();
const params = useMemo(() => ({
collections: searchParams.get('collections')?.split(','),
bbox: searchParams.get('bbox')?.split(',').map(Number) as Bbox,
datetime: searchParams.get('datetime'),
}), [searchParams]);
const { results } = useStacSearchDeclarative({ params });
// Search params stay in sync with URL
return <Results items={results?.features} />;
}
3. With Custom Search Link
function CustomEndpointSearch({ searchLink, bbox }) {
const { results, isLoading } = useStacSearchDeclarative({
params: { bbox, limit: 100 },
searchLink, // Use specific search endpoint
});
return <div>{/* ... */}</div>;
}
4. Conditional Search
function ConditionalSearch({ enabled, collections }) {
const { results } = useStacSearchDeclarative({
params: { collections },
enabled, // Only search when enabled is true
});
// Useful for not searching until user clicks "Search"
}
Coexistence with Stateful Hook
Both patterns should coexist:
// Stateful (existing) - for interactive search forms
export { useStacSearch } from './hooks/useStacSearch';
// Declarative (new) - for declarative patterns
export { useStacSearchDeclarative } from './hooks/useStacSearchDeclarative';
Users choose based on their needs:
- Stateful: Building search forms with stepwise input
- Declarative: URL-synced search, controlled components, derived state
Infinite Scroll Support
For infinite scroll/pagination:
function useStacSearchInfinite(options: UseStacSearchDeclarativeOptions) {
return useInfiniteQuery({
queryKey: ['stac-search-infinite', options.params],
queryFn: ({ pageParam }) => {
// Fetch using pageParam (next link)
},
initialPageParam: options.searchLink,
getNextPageParam: (lastPage) =>
lastPage.links?.find(l => l.rel === 'next'),
});
}
Benefits
- ✅ Declarative API matches React patterns
- ✅ Automatic re-search on parameter changes
- ✅ Easy URL parameter synchronization
- ✅ Simpler testing and reasoning
- ✅ Works with or without context
- ✅ Maintains backward compatibility
- ✅ Supports custom search endpoints
Breaking Changes
None - this adds a new hook alongside the existing one.
Migration Path
Existing code using stateful useStacSearch continues to work. New code can adopt useStacSearchDeclarative when it fits better.
Related Issues
- #5 - Add Standalone Hooks (Context-Free Mode)
- #10 - Add Infinite Query Support
Testing Requirements
- Test automatic re-search on param changes
- Test with URL parameters
- Test enabled/disabled state
- Test with custom search links
- Test with and without StacApi context
- Test pagination link extraction
- Test error handling
- Test with all search parameter types
Documentation Requirements
- Document both stateful and declarative patterns
- Provide guidance on when to use each
- Show URL synchronization example
- Document infinite scroll pattern
- Show migration examples
- Explain trade-offs between approaches
- Dominant language
- TypeScript
- Stars
- 34
- Forks
- 4
- PR merge metrics
- No merged PRs in 30d
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from developmentseed/stac-react
-
Difficulty 3/5 1-2 days Newbie friendliness 55/100
-
Difficulty 5/5 Over a week Newbie friendliness 35/100
-
Difficulty 5/5 Over a week Newbie friendliness 35/100
-
Difficulty 5/5 Over a week Newbie friendliness 30/100
-
Difficulty 5/5 Over a week Newbie friendliness 42/100
All issues in developmentseed/stac-react
Similar issues
-
Browser Waiting for: Product Owner
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
getsentry/sentry-javascript#24577 · 1 comment ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
agilepathway/label-checker#640 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
copse-dev/agent-pane#2953 ·
-
agentic-workflows
Difficulty 1/5 Under an hour Newbie friendliness 85/100
githubnext/rig#534 ·
-
automation missing-model model-sync provider:pioneer
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
anomalyco/models.dev#7701 ·