OrderBySelect is an auto-generated component for selecting sort criteria in list pages. Itβs generated based on the backendβs OrderBy enum with auto-mapped labels.
Core Features
Auto-generated Auto-generated from Entity definition OrderBy enum based
Localized Labels Auto label mapping βID Latestβ, βCreated Ascendingβ, etc.
useListParams Integration register pattern support Auto URL sync
Customizable Scaffolding file Freely modifiable
Auto-generation Conditions
OrderBySelect is auto-generated when the OrderBy enum is defined in the backend.
Backend Definition
// user.types.ts (backend)
import { z } from "zod" ;
export const UserOrderBy = z . enum ([
"id-desc" ,
"id-asc" ,
"created_at-desc" ,
"created_at-asc" ,
"username-asc" ,
"username-desc" ,
]);
export const UserListParams = UserBaseListParams . extend ({
orderBy: UserOrderBy . optional (),
});
Auto-generated Files
Location : web/src/components/user/UserOrderBySelect.tsx
Auto-generated labels : web/src/services/sonamu.generated.ts
export const UserOrderByLabel = {
"id-desc" : "ID Latest" ,
"id-asc" : "ID Oldest" ,
"created_at-desc" : "Created Latest" ,
"created_at-asc" : "Created Oldest" ,
"username-asc" : "Name Ascending" ,
"username-desc" : "Name Descending" ,
};
Label Generation Rules
id-desc β βID Latestβ
created_at-asc β βCreated Oldestβ
username-desc β βName Descendingβ
Generated by combining field name and sort direction (asc/desc).
Basic Usage
Using with useListParams
import { useListParams } from "@sonamu-kit/react-components" ;
import { UserOrderBySelect } from "@/components/user/UserOrderBySelect" ;
import { UserOrderBy } from "@/services/sonamu.generated" ;
export function UserListPage () {
const { register } = useListParams ( UserListParams , {
num: 24 ,
page: 1 ,
orderBy: UserOrderBy . options [ 0 ], // Default: "id-desc"
});
return (
< div className = "flex gap-2" >
< UserOrderBySelect { ... register (" orderBy ")} />
</ div >
);
}
How it works :
User selects sort option
register("orderBy") automatically updates URL
listParams.orderBy is passed when calling API
Backend applies sorting
export type UserOrderBySelectProps = {
value ?: string ;
onValueChange ?: ( value : string | null | undefined ) => void ;
placeholder ?: string ;
textPrefix ?: string ;
clearable ?: boolean ;
disabled ?: boolean ;
className ?: string ;
};
Basic Usage (without register)
import { useState } from "react" ;
import { UserOrderBySelect } from "@/components/user/UserOrderBySelect" ;
export function UserListPage () {
const [ orderBy , setOrderBy ] = useState < string >( "id-desc" );
return (
< UserOrderBySelect
value = { orderBy }
onValueChange = {(value) => setOrderBy ( value ? ? "id-desc" )}
placeholder = "Sort by"
/>
);
}
Props Details
placeholder
Text displayed when nothing is selected.
< UserOrderBySelect
{ ... register ( "orderBy" )}
placeholder = "Select sort" // Default: "Sort"
/>
textPrefix
Prefix added before each option.
< UserOrderBySelect
{ ... register ( "orderBy" )}
textPrefix = "Sort: "
/>
// Rendered result:
// - Sort: ID Latest
// - Sort: ID Oldest
// - Sort: Created Latest
textPrefix Use Case Useful when you want to clearly display current sort at the top of list pages:
clearable
Adds an βAllβ option to reset sorting.
< UserOrderBySelect
{ ... register ( "orderBy" )}
clearable
/>
// Rendered result:
// - All β Added by clearable
// - ID Latest
// - ID Oldest
// - ...
Use scenarios :
Want to return to default sorting
Want to see data in database order without sorting
disabled
Disables the component.
< UserOrderBySelect
{ ... register ( "orderBy" )}
disabled = { isLoading } // Can't change while loading
/>
className
Add Tailwind CSS classes.
< UserOrderBySelect
{ ... register ( "orderBy" )}
className = "w-[200px] h-8 bg-white border-gray-300 text-xs"
/>
Real-world Examples
Complete List Filter
import { useListParams } from "@sonamu-kit/react-components" ;
import { Input } from "@sonamu-kit/react-components/components" ;
import { UserService } from "@/services/services.generated" ;
import { UserOrderBySelect } from "@/components/user/UserOrderBySelect" ;
import { UserSearchFieldSelect } from "@/components/user/UserSearchFieldSelect" ;
import { UserRoleSelect } from "@/components/user/UserRoleSelect" ;
export function UserListPage () {
const { listParams , register } = useListParams ( UserListParams , {
num: 24 ,
page: 1 ,
search: "id" as const ,
keyword: "" ,
orderBy: "id-desc" as const ,
role: undefined ,
});
const { data } = UserService . useUsers ( "A" , listParams );
return (
< div >
{ /* Filter section */ }
< div className = "flex gap-2 mb-4" >
{ /* Search */ }
< UserSearchFieldSelect
{ ... register (" search ")}
className = "w-32"
/>
< Input
{ ... register (" keyword ")}
placeholder = "Keyword"
className = "w-64"
/>
{ /* Role filter */ }
< UserRoleSelect
{ ... register (" role ")}
clearable
/>
{ /* Sort */ }
< UserOrderBySelect
{ ... register (" orderBy ")}
textPrefix = "Sort: "
className = "w-[200px]"
/>
</ div >
{ /* Table */ }
< table >
{ /* ... */ }
</ table >
</ div >
);
}
Mobile Responsive
< div className = "flex flex-col sm:flex-row gap-2" >
< UserOrderBySelect
{ ... register ( "orderBy" )}
className = "w-full sm:w-[200px]" // Mobile: full width, Desktop: 200px
/>
</ div >
Display Number of Sort Options
import { UserOrderBy } from "@/services/sonamu.generated" ;
< div className = "flex items-center gap-2" >
< span className = "text-sm text-gray-500" >
{ UserOrderBy . options . length } sort options
</ span >
< UserOrderBySelect { ... register ( "orderBy" )} />
</ div >
Customization
OrderBySelect is a scaffolding file , so you can modify it freely.
Change Labels
// Modify UserOrderBySelect.tsx
import { UserOrderByLabel } from "@/services/sonamu.generated" ;
// Create custom label object
const customLabels = {
... UserOrderByLabel ,
"id-desc" : "Latest First" , // Default: "ID Latest"
"created_at-asc" : "Oldest" , // Default: "Created Oldest"
};
export function UserOrderBySelect ({ ... }) {
return (
< Select ...>
< SelectContent >
{ validOptions . map (( key ) => (
< SelectItem key = { key } value = { key } >
{( textPrefix ?? "") + customLabels [ key ]}
</ SelectItem >
))}
</ SelectContent >
</ Select >
);
}
Add Icons
import ArrowUpIcon from "~icons/lucide/arrow-up" ;
import ArrowDownIcon from "~icons/lucide/arrow-down" ;
const getIcon = ( key : string ) => {
if ( key . endsWith ( "-asc" )) return < ArrowUpIcon className = "w-3 h-3" />;
if ( key . endsWith ( "-desc" )) return < ArrowDownIcon className = "w-3 h-3" />;
return null ;
};
< SelectItem key = { key } value = { key } >
< div className = "flex items-center gap-2" >
{ getIcon ( key )}
{ customLabels [ key ]}
</ div >
</ SelectItem >
Hide Specific Options
// Show only "username" sort options
const visibleOptions = validOptions . filter ( key =>
key . includes ( "username" )
);
< SelectContent >
{ visibleOptions . map (( key ) => (
< SelectItem key = { key } value = { key } >
{ UserOrderByLabel [ key ]}
</ SelectItem >
))}
</ SelectContent >
Grouping
const groupedOptions = {
"Basic" : [ "id-desc" , "id-asc" ],
"Date" : [ "created_at-desc" , "created_at-asc" ],
"Name" : [ "username-asc" , "username-desc" ],
};
< SelectContent >
{ Object . entries ( groupedOptions ). map (([ group , options ]) => (
< Fragment key = { group } >
< SelectLabel >{ group } </ SelectLabel >
{ options . map (( key ) => (
< SelectItem key = { key } value = { key } >
{ UserOrderByLabel [ key ]}
</ SelectItem >
))}
</ Fragment >
))}
</ SelectContent >
Backend Processing
The value selected in OrderBySelect is passed to the backend for actual sorting.
Handling in Model
// user.model.ts (backend)
async findMany < T extends UserSubsetKey , LP extends UserListParams > (
subset : T ,
rawParams ?: LP ,
): Promise < ListResult < LP , UserSubsetMapping [ T ] >> {
const params = {
num: 24 ,
page: 1 ,
orderBy: "id-desc" as const ,
... rawParams ,
};
const { qb } = this.getSubsetQueries(subset);
// orderBy processing
if (params.orderBy === "id-desc" ) {
qb . orderBy ( "users.id" , "desc" );
} else if (params.orderBy === "id-asc" ) {
qb . orderBy ( "users.id" , "asc" );
} else if (params.orderBy === "created_at-desc" ) {
qb . orderBy ( "users.created_at" , "desc" );
} else if (params.orderBy === "created_at-asc" ) {
qb . orderBy ( "users.created_at" , "asc" );
} else if (params.orderBy === "username-asc" ) {
qb . orderBy ( "users.username" , "asc" );
} else if (params.orderBy === "username-desc" ) {
qb . orderBy ( "users.username" , "desc" );
} else {
exhaustive ( params . orderBy ); // Ensure type safety
}
return this.executeSubsetQuery({ subset , qb , params });
}
exhaustive Pattern
exhaustive() validates at compile time that all cases are handled.
import { exhaustive } from "sonamu" ;
// Adding new option "email-asc" to OrderBy enum
// Compile error occurs β Developer immediately knows about omission
else {
exhaustive ( params . orderBy ); // β Type error: "email-asc" not handled
}
Regeneration
OrderBySelect is not auto-regenerated after initial generation.
When Regeneration is Needed
Component file was deleted
Entityβs OrderBy enum significantly changed
Regeneration Method
# Delete component
rm web/src/components/user/UserOrderBySelect.tsx
# Restart dev server (auto-regenerates)
pnpm dev
Caution on Regeneration Customized content will be lost on regeneration. Back up or document important modifications separately.
Troubleshooting
OrderBySelect Not Generated
Cause : OrderBy enum not defined in backend
Solution :
// Add to user.types.ts (backend)
export const UserOrderBy = z . enum ([
"id-desc" ,
"id-asc" ,
]);
export const UserListParams = UserBaseListParams . extend ({
orderBy: UserOrderBy . optional (),
});
Labels Not Showing in Localized Language
Cause : sonamu.generated.ts not regenerated
Solution :
# Delete sonamu.lock and re-sync
rm api/sonamu.lock
pnpm sonamu sync
Sorting Not Applied Despite Selection
Cause : orderBy handling missing in backend Model
Solution : Handle params.orderBy in user.model.tsβs findMany.