@indxsearch/intrface
v1.1.4
Published
Indx Search interface components for React. Provides ready-to-use filter panels, search inputs, faceted navigation, and sorting components. Built on @indxsearch/systm.
Maintainers
Readme
@indxsearch/intrface
A powerful, flexible React search UI library for INDX Search API.
Features
- 🔍 Full-text search with fuzzy matching and typo tolerance
- 🎯 Faceted filtering - Value filters (exact match) and range filters (numeric)
- 📊 Real-time facet counts - Dynamic aggregations that update with search
- 📱 Mobile-responsive - Built-in responsive design
- ⚡ Debounced searches - Optimized performance
- 🎨 Customizable rendering - Full control over result display
- 🔒 Secure authentication - Session-based authentication with automatic login
Installation
npm install @indxsearch/intrface @indxsearch/systm @indxsearch/pixlQuick Start
1. Set Up Environment Variables
Create a .env.local file in your project root:
Option A: Using Bearer Token (Recommended for Production)
VITE_INDX_URL=https://your-indx-server.com
VITE_INDX_TOKEN=your-jwt-bearer-token-hereOption B: Using Email/Password (Quick Start)
VITE_INDX_URL=https://your-indx-server.com
[email protected]
VITE_INDX_PASSWORD=yourpasswordFor local development:
VITE_INDX_URL=http://localhost:5001
# Then add either token or email/password as shown aboveSecurity Notes:
- Never commit
.env.localto version control - Store credentials/tokens securely in environment variables
- For production deployments, prefer bearer token authentication
2. Import Styles
Import the CSS file in your app entry point:
import '@indxsearch/intrface/styles.css';3. Basic Implementation
import { SearchProvider, SearchInput, SearchResults } from '@indxsearch/intrface';
export default function SearchPage() {
return (
<SearchProvider
url={import.meta.env.VITE_INDX_URL}
email={import.meta.env.VITE_INDX_EMAIL}
password={import.meta.env.VITE_INDX_PASSWORD}
dataset="products"
>
<SearchInput placeholder="Search products..." />
<SearchResults
fields={['name', 'description', 'category']}
resultsPerPage={10}
>
{(item) => (
<div>
<h3>{item.name}</h3>
<p>{item.description}</p>
</div>
)}
</SearchResults>
</SearchProvider>
);
}Using different datasets on different pages:
// products page
<SearchProvider url={url} email={email} password={password} dataset="products">
{/* ... */}
</SearchProvider>
// articles page
<SearchProvider url={url} email={email} password={password} dataset="articles">
{/* ... */}
</SearchProvider>Authentication
The library supports two authentication methods: bearer token and email/password login.
Method 1: Bearer Token (Recommended for Production)
Use a pre-authenticated JWT bearer token. This is ideal when you have a backend that generates tokens for your users.
Environment setup:
VITE_INDX_URL=https://your-indx-server.com
VITE_INDX_TOKEN=your-jwt-bearer-token-hereUsage:
<SearchProvider
url={import.meta.env.VITE_INDX_URL}
preAuthenticatedToken={import.meta.env.VITE_INDX_TOKEN}
dataset="products"
>
{/* Your search UI */}
</SearchProvider>When to use:
- ✅ Production applications with backend authentication
- ✅ When you have existing token infrastructure
- ✅ For better security (tokens can have expiration, limited scope)
- ✅ When you want to avoid storing passwords client-side
Method 2: Email/Password (Quick Start & Development)
Automatically logs in when the app initializes using email and password.
Environment setup:
VITE_INDX_URL=https://your-indx-server.com
[email protected]
VITE_INDX_PASSWORD=yourpasswordUsage:
<SearchProvider
url={import.meta.env.VITE_INDX_URL}
email={import.meta.env.VITE_INDX_EMAIL}
password={import.meta.env.VITE_INDX_PASSWORD}
dataset="products"
>
{/* Your search UI */}
</SearchProvider>How it works:
- You provide email and password to
SearchProvider - On mount, the library automatically calls the Login API endpoint
- A fresh session token is obtained and used for all subsequent requests
- No manual token management required
When to use:
- ✅ Quick prototyping and development
- ✅ Demo applications
- ✅ When you don't have token infrastructure yet
Security Best Practices:
- Store credentials/tokens in environment variables (
.env.local) - Never commit
.env.localto version control - Use secure HTTPS connections in production
- For production, prefer bearer token authentication
Error Handling
The library includes comprehensive error handling with helpful console messages:
Automatic Error Detection
The SearchProvider automatically validates:
- ✅ Authentication (bearer token or email/password)
- ✅ Login success and token retrieval (when using email/password)
- ✅ Dataset existence and status
- ✅ Dataset readiness (indexing complete)
- ✅ Empty dataset warnings
- ✅ Network connectivity
All errors include:
- Clear error messages
- Specific problem identification
- Actionable fix suggestions
- Example commands to resolve issues
Error Boundary (Optional)
Wrap your search interface with SearchErrorBoundary for graceful error handling:
import { SearchErrorBoundary, SearchProvider } from '@indxsearch/intrface';
<SearchErrorBoundary>
<SearchProvider url={url} email={email} password={password} dataset={dataset}>
{/* Your search UI */}
</SearchProvider>
</SearchErrorBoundary>Custom error UI:
<SearchErrorBoundary
fallback={(error, reset) => (
<div>
<h2>Search Error</h2>
<p>{error.message}</p>
<button onClick={reset}>Try Again</button>
</div>
)}
>
<SearchProvider url={url} email={email} password={password} dataset={dataset}>
{children}
</SearchProvider>
</SearchErrorBoundary>Console Error Messages
All errors show in the browser console with emoji indicators:
- ✅ = Success
- 🔍 = Checking something
- ⚠️ = Warning (non-critical)
- ❌ = Error (needs fixing)
- 💡 = Helpful suggestion
Example:
[Auth] ❌ Dataset "products" not found (404)
[Auth] 💡 Available datasets can be checked with: curl -X GET ...
[Auth] 💡 Make sure you spelled the dataset name correctlyAdding Filters
Value Filters (Exact Match)
import { ValueFilterPanel } from '@indxsearch/intrface';
<SearchProvider {...authProps}>
<SearchInput />
{/* Simple checkbox list */}
<ValueFilterPanel
field="category"
label="Category"
/>
{/* Button-style filters */}
<ValueFilterPanel
field="brand"
label="Brand"
displayType="button"
layout="grid"
/>
<SearchResults {...resultsProps}>
{renderItem}
</SearchResults>
</SearchProvider>Range Filters (Numeric)
import { RangeFilterPanel } from '@indxsearch/intrface';
<RangeFilterPanel
field="price"
label="Price Range"
min={0}
max={1000}
/>Active Filters Display
import { ActiveFiltersPanel } from '@indxsearch/intrface';
<ActiveFiltersPanel />Full Example with Filters
import {
SearchProvider,
SearchInput,
SearchResults,
ValueFilterPanel,
RangeFilterPanel,
ActiveFiltersPanel,
SortByPanel,
} from '@indxsearch/intrface';
export default function AdvancedSearch() {
return (
<SearchProvider
url={import.meta.env.VITE_INDX_URL}
email={import.meta.env.VITE_INDX_EMAIL}
password={import.meta.env.VITE_INDX_PASSWORD}
dataset="products"
allowEmptySearch={true}
enableFacets={true}
maxResults={20}
>
<div style={{ display: 'flex', gap: '2rem' }}>
{/* Sidebar with filters */}
<aside style={{ width: '250px' }}>
<ActiveFiltersPanel />
<SortByPanel displayType="radio" />
<ValueFilterPanel field="category" label="Category" />
<ValueFilterPanel field="brand" label="Brand" displayType="button" />
<RangeFilterPanel field="price" label="Price" />
</aside>
{/* Main content */}
<main style={{ flex: 1 }}>
<SearchInput placeholder="Search products..." showFocus={true} />
<SearchResults
fields={['name', 'description', 'price', 'category', 'brand']}
resultsPerPage={20}
>
{(item) => (
<div style={{ padding: '1rem', borderBottom: '1px solid #eee' }}>
<h3>{item.name}</h3>
<p>{item.description}</p>
<div>
<strong>${item.price}</strong> • {item.category}
</div>
</div>
)}
</SearchResults>
</main>
</div>
</SearchProvider>
);
}API Reference
SearchProvider Props
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| url | string | ✅ | - | INDX server URL |
| preAuthenticatedToken | string | ⚠️ | - | Bearer token for authentication (either this OR email/password) |
| email | string | ⚠️ | - | User email for authentication (either this OR token) |
| password | string | ⚠️ | - | User password for authentication (either this OR token) |
| dataset | string | ✅ | - | Dataset name |
| allowEmptySearch | boolean | ❌ | false | Show results without query |
| enableFacets | boolean | ❌ | true | Enable faceted search |
| maxResults | number | ❌ | 10 | Max results per search |
| facetDebounceDelayMillis | number | ❌ | 500 | Debounce delay for facet updates |
| coverageDepth | number | ❌ | 500 | Search depth for fuzzy matching |
| removeDuplicates | boolean | ❌ | false | Remove duplicate results |
| enableDebugLogs | boolean | ❌ | false | Enable detailed console logging |
SearchInput Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| placeholder | string | 'Search...' | Input placeholder text |
| showClear | boolean | true | Show clear button |
| showFocus | boolean | false | Show focus ring |
| inputSize | 'micro' \| 'default' | 'default' | Input size |
SearchResults Props
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| fields | string[] | ✅ | Document fields to fetch |
| resultsPerPage | number | ✅ | Results per page |
| children | (item: any) => ReactNode | ✅ | Render function for each result |
ValueFilterPanel Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| field | string | ✅ | Field name to filter on |
| label | string | ❌ | Display label |
| displayType | 'checkbox' \| 'button' | 'checkbox' | Filter UI style |
| layout | 'list' \| 'grid' | 'list' | Layout style |
| limit | number | undefined | Max filters to show |
| startCollapsed | boolean | false | Start collapsed |
| showCount | boolean | true | Show facet counts |
RangeFilterPanel Props
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| field | string | ✅ | Field name to filter on |
| label | string | ❌ | Display label |
| min | number | ❌ | Minimum value |
| max | number | ❌ | Maximum value |
Troubleshooting
"Login failed" error
Problem: Authentication credentials are invalid
Solutions:
- Verify your email and password are correct
- Check that the credentials match your INDX account
- Ensure the INDX server URL is correct
- Check browser console for detailed error messages
"401 Unauthorized" errors
Problem: Authentication failed or token is invalid
Solutions:
- If using bearer token: Verify the token is valid and not expired. Generate a new token if needed.
- If using email/password: Refresh the page to get a new session token (automatic login)
- Check that the token/credentials are correctly set in your environment variables
- Verify the server is running and accessible
- Check server logs for authentication issues
"Failed to fetch" errors
Problem: Cannot connect to INDX server
Solutions:
- Verify the server URL is correct
- Check if the server is running (for local:
http://localhost:5001) - Ensure CORS is configured on the server
- Check browser console for detailed error
Results not showing
Problem: Empty results even with data
Solutions:
- Verify dataset name is correct
- Check if dataset is indexed (use GetStatus endpoint)
- Ensure fields are configured as indexable/facetable
- Try
allowEmptySearch={true}to see all results
Filters not working
Problem: Filters don't update results
Solutions:
- Ensure fields are configured as filterable/facetable in your dataset
- Check browser console for errors
- Verify field names match your dataset
Examples
Example 1: E-commerce Search
<SearchProvider url={url} email={email} password={password} dataset="products">
<div className="search-page">
<SearchInput placeholder="Search products..." />
<div className="filters">
<ValueFilterPanel field="category" label="Category" />
<ValueFilterPanel field="brand" label="Brand" displayType="button" />
<RangeFilterPanel field="price" label="Price" min={0} max={1000} />
<ValueFilterPanel field="inStock" label="In Stock" />
</div>
<SearchResults fields={['name', 'price', 'image']} resultsPerPage={24}>
{(product) => (
<ProductCard
name={product.name}
price={product.price}
image={product.image}
/>
)}
</SearchResults>
</div>
</SearchProvider>Example 2: Document Search
<SearchProvider url={url} email={email} password={password} dataset="documents">
<SearchInput placeholder="Search documents..." />
<ValueFilterPanel field="docType" label="Type" />
<ValueFilterPanel field="author" label="Author" />
<SearchResults fields={['title', 'content', 'date']} resultsPerPage={10}>
{(doc) => (
<article>
<h2>{doc.title}</h2>
<p>{doc.content.substring(0, 200)}...</p>
<small>{new Date(doc.date).toLocaleDateString()}</small>
</article>
)}
</SearchResults>
</SearchProvider>Support
- Documentation: docs.indx.co
