Relation selector components provide a powerful way to select related entities in your dashboard forms. They support both single and multi-selection modes with built-in search, infinite scroll pagination, and complete TypeScript type safety.
Features
Real-time Search: Debounced search with customizable filters
Infinite Scroll: Automatic pagination loading 25 items by default
Single/Multi Select: Easy toggle between selection modes
Type Safe: Full TypeScript support with generic types
Customizable: Pass your own GraphQL queries and field mappings
Accessible: Built with Base UI primitives
Components Overview
The relation selector system consists of three main components:
RelationSelector: The abstract base component that handles all core functionality
SingleRelationInput: Convenient wrapper for single entity selection
MultiRelationInput: Convenient wrapper for multiple entity selection
import { MultiRelationInput, DashboardFormComponentProps } from '@vendure/dashboard';export function ProductMultiSelectorComponent({ value, onChange, disabled }: DashboardFormComponentProps) { return ( <MultiRelationInput value={value || []} onChange={onChange} config={productConfig} // Same config as above disabled={disabled} /> );}
Configuration Options
The createRelationSelectorConfig function accepts these options:
Tsx
interface RelationSelectorConfig<T> { /** The GraphQL query document for fetching items */ listQuery: DocumentNode; /** The property key for the entity ID */ idKey: keyof T; /** The property key for the display label (used as fallback when label function not provided) */ labelKey: keyof T; /** Number of items to load per page (default: 25) */ pageSize?: number; /** Placeholder text for the search input */ placeholder?: string; /** Whether to enable multi-select mode */ multiple?: boolean; /** Custom filter function for search */ buildSearchFilter?: (searchTerm: string) => any; /** Custom label renderer function for rich display */ label?: (item: T) => React.ReactNode;}
Rich Label Display
The label prop allows you to customize how items are displayed in both the dropdown and selected item chips. This enables rich content like images, badges, and multi-line information.
Select only needed fields: Include only the fields you actually use to improve performance
Use fragments: Create reusable fragments for consistent data fetching
Optimize search filters: Use database indexes for the fields you search on
Tsx
// Good: Minimal required fieldsconst productListQuery = graphql(` query GetProductsForSelection($options: ProductListOptions) { products(options: $options) { items { id name # Only include what you need } totalItems } }`);// Avoid: Over-fetching unnecessary dataconst productListQuery = graphql(` query GetProductsForSelection($options: ProductListOptions) { products(options: $options) { items { id name description featuredAsset { ... } # Only if you display it variants { ... } # Usually not needed for selection # etc. } totalItems } }`);
Performance Tips
Appropriate page sizes: Balance between fewer requests and faster initial loads
Debounced search: The default 300ms debounce prevents excessive API calls
Caching: Queries are automatically cached by TanStack Query
Tsx
const config = createRelationSelectorConfig({ listQuery: myQuery, idKey: 'id', labelKey: 'name', pageSize: 25, // Good default, adjust based on your data buildSearchFilter: (term: string) => ({ // Use indexed fields for better performance name: { contains: term }, }),});
Type Safety
Leverage TypeScript generics for full type safety:
Tsx
interface MyEntity { id: string; title: string; status: 'ACTIVE' | 'INACTIVE';}const myEntityConfig = createRelationSelectorConfig<MyEntity>({ listQuery: myEntityQuery, idKey: 'id', // ✅ TypeScript knows this must be a key of MyEntity labelKey: 'title', // ✅ TypeScript validates this field exists buildSearchFilter: (term: string) => ({ title: { contains: term }, // ✅ Auto-completion and validation }),});
Rich Label Design
When using the label prop for custom rendering:
Keep it simple: Avoid overly complex layouts that might impact performance
Handle missing data: Always check for optional fields before rendering
Maintain accessibility: Use proper semantic HTML and alt text for images
Consider mobile: Ensure labels work well on smaller screens
Error: Cannot query field "myEntities" on type "Query"
Solution: Ensure your GraphQL query field name matches your schema definition exactly.
2. Empty results despite data existing
Tsx
// Problem: Wrong field used for searchbuildSearchFilter: (term: string) => ({ wrongField: { contains: term }, // This field doesn't exist});// Solution: Use correct field namesbuildSearchFilter: (term: string) => ({ name: { contains: term }, // Correct field name});
3. TypeScript errors with config
Tsx
// Problem: Missing type parameterconst config = createRelationSelectorConfig({ // TypeScript can't infer the entity type});// Solution: Provide explicit type or use proper typingconst config = createRelationSelectorConfig<MyEntityType>({ // Now TypeScript knows the shape of your entity});
Performance Issues
If you experience slow loading:
Check your GraphQL query: Ensure it's optimized and uses appropriate filters
Verify database indexes: Make sure searched fields are indexed