> ## Documentation Index
> Fetch the complete documentation index at: https://sonamu.cartanova.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# OrderBySelect

> Sort option selection component usage

`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

<CardGroup cols={2}>
  <Card title="Auto-generated" icon="wand-magic-sparkles">
    Auto-generated from Entity definition

    OrderBy enum based
  </Card>

  <Card title="Localized Labels" icon="language">
    Auto label mapping

    "ID Latest", "Created Ascending", etc.
  </Card>

  <Card title="useListParams Integration" icon="link">
    register pattern support

    Auto URL sync
  </Card>

  <Card title="Customizable" icon="gear">
    Scaffolding file

    Freely modifiable
  </Card>
</CardGroup>

## Auto-generation Conditions

OrderBySelect is auto-generated when the `OrderBy` enum is defined in the backend.

### Backend Definition

```typescript theme={null}
// 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`

```typescript theme={null}
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",
};
```

<Info>
  **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`).
</Info>

## Basic Usage

### Using with useListParams

```typescript theme={null}
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**:

1. User selects sort option
2. `register("orderBy")` automatically updates URL
3. `listParams.orderBy` is passed when calling API
4. Backend applies sorting

### Props

```typescript theme={null}
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)

```typescript theme={null}
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.

```typescript theme={null}
<UserOrderBySelect
  {...register("orderBy")}
  placeholder="Select sort"  // Default: "Sort"
/>
```

### textPrefix

Prefix added before each option.

```typescript theme={null}
<UserOrderBySelect
  {...register("orderBy")}
  textPrefix="Sort: "
/>

// Rendered result:
// - Sort: ID Latest
// - Sort: ID Oldest
// - Sort: Created Latest
```

<Info>
  **textPrefix Use Case**

  Useful when you want to clearly display current sort at the top of list pages:

  ```
  [Sort: ID Latest ▼]
  ```
</Info>

### clearable

Adds an "All" option to reset sorting.

```typescript theme={null}
<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.

```typescript theme={null}
<UserOrderBySelect
  {...register("orderBy")}
  disabled={isLoading}  // Can't change while loading
/>
```

### className

Add Tailwind CSS classes.

```typescript theme={null}
<UserOrderBySelect
  {...register("orderBy")}
  className="w-[200px] h-8 bg-white border-gray-300 text-xs"
/>
```

## Real-world Examples

### Complete List Filter

```typescript theme={null}
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

```typescript theme={null}
<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

```typescript theme={null}
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

```typescript theme={null}
// 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

```typescript theme={null}
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

```typescript theme={null}
// 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

```typescript theme={null}
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

```typescript theme={null}
// 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.

```typescript theme={null}
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

1. Component file was deleted
2. Entity's OrderBy enum significantly changed

### Regeneration Method

```bash theme={null}
# Delete component
rm web/src/components/user/UserOrderBySelect.tsx

# Restart dev server (auto-regenerates)
pnpm dev
```

<Warning>
  **Caution on Regeneration**

  Customized content will be lost on regeneration. Back up or document important modifications separately.
</Warning>

## Troubleshooting

### OrderBySelect Not Generated

**Cause**: `OrderBy` enum not defined in backend

**Solution**:

```typescript theme={null}
// 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**:

```bash theme={null}
# Force re-sync (removes sonamu.lock and runs a full sync)
pnpm sonamu sync --force
```

### Sorting Not Applied Despite Selection

**Cause**: `orderBy` handling missing in backend Model

**Solution**: Handle `params.orderBy` in `user.model.ts`'s `findMany`.

## Related Documentation

* [auto-generated-overview](/en/frontend-integration/react-components/auto-generated-overview) - Auto-generated components overview
* [SearchFieldSelect](/en/frontend-integration/react-components/search-components) - Search field selection
* [useListParams](/en/frontend-integration/core-hooks/use-list-params) - List filtering hook
