> ## 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.

# ID Async Select

> Async select component for selecting relational data

Learn how to use the ID Async Select component for handling foreign key relationships.

## ID Async Select Overview

<CardGroup cols={2}>
  <Card title="Async Loading" icon="clock">
    API calls

    Autocomplete
  </Card>

  <Card title="Type Safe" icon="shield-check">
    Entity based

    ID type guaranteed
  </Card>

  <Card title="Search Support" icon="magnifying-glass">
    Real-time filtering

    Debounce
  </Card>

  <Card title="Multiple Selection" icon="list-check">
    Single/Multiple modes

    Array return
  </Card>
</CardGroup>

## What is ID Async Select?

### Problem: Complexity of Foreign Key Selection

When handling foreign key relationships in databases, the frontend needs to **select related entities**.

**Problems with traditional approach**:

```typescript theme={null}
// ❌ Load all users at once (inefficient)
const [users, setUsers] = useState([]);

useEffect(() => {
  userService.list({ pageSize: 1000 }).then(({ users }) => {
    setUsers(users); // Load all 1000 users!
  });
}, []);

return (
  <select>
    {users.map((user) => (
      <option key={user.id} value={user.id}>
        {user.username}
      </option>
    ))}
  </select>
);
```

**Problems**:

1. **Performance**: Loading time increases with more data
2. **Memory**: All unnecessary data loaded into memory
3. **UX**: Hard for users to find desired items
4. **Scalability**: Can't handle continuous data growth

### Solution: ID Async Select

Sonamu's ID Async Select **loads asynchronously only when needed** and provides **search functionality**.

```typescript theme={null}
// ✅ Load only needed data based on search terms
import { IdAsyncSelect } from "@sonamu-kit/react-components";
import { UserAsyncIdConfig } from "@/services/services.generated";

<IdAsyncSelect
  config={UserAsyncIdConfig}
  subset="A"
  value={selectedUserId}
  onValueChange={setSelectedUserId}
/>
```

**Benefits**:

1. ✨ **Performance**: Load minimum initially, only what's needed on search
2. ✨ **User experience**: Quick selection with autocomplete
3. ✨ **Type safe**: Entity type automatically applied
4. ✨ **Scalability**: No problem regardless of data volume

## Basic Usage

### Required Props

ID Async Select uses these two core props:

* `config`: AsyncIdConfig object auto-generated from `services.generated.ts`
* `subset`: Subset key to query (e.g., "A", "D", etc.)

```typescript theme={null}
import { IdAsyncSelect } from "@sonamu-kit/react-components";
import { UserAsyncIdConfig } from "@/services/services.generated";

<IdAsyncSelect
  config={UserAsyncIdConfig}
  subset="A"
  value={userId}
  onValueChange={setUserId}
/>
```

### Single Selection

Select one entity.

```typescript theme={null}
import { IdAsyncSelect } from "@sonamu-kit/react-components";
import { UserAsyncIdConfig } from "@/services/services.generated";
import { useState } from "react";

function PostForm() {
  const [authorId, setAuthorId] = useState<number | null>(null);

  return (
    <div>
      <label>Author</label>
      <IdAsyncSelect
        config={UserAsyncIdConfig}
        subset="A"
        value={authorId}
        onValueChange={setAuthorId}
        displayField="username"
        placeholder="Select author..."
      />
    </div>
  );
}
```

**How it works**:

1. Call search API when user types
2. Display search results in dropdown
3. Only save ID when selected (memory efficient)

### Multiple Selection

Select multiple entities.

```typescript theme={null}
import { TagAsyncIdConfig } from "@/services/services.generated";

function PostForm() {
  const [tagIds, setTagIds] = useState<number[]>([]);

  return (
    <div>
      <label>Tags</label>
      <IdAsyncSelect
        config={TagAsyncIdConfig}
        subset="A"
        value={tagIds}
        onValueChange={setTagIds}
        displayField="name"
        multiple
        placeholder="Select tags..."
      />
    </div>
  );
}
```

**Multiple selection features**:

* Display selected items as tags
* Remove individually with X button
* Return IDs as array (`number[]`)

### Setting Initial Values

Set initial values when editing existing data.

```typescript theme={null}
function EditPostForm({ post }: { post: Post }) {
  const [authorId, setAuthorId] = useState(post.author_id);

  return (
    <IdAsyncSelect
      config={UserAsyncIdConfig}
      subset="A"
      value={authorId}
      onValueChange={setAuthorId}
      displayField="username"
    />
  );
}
```

**Initial value handling**:

* Passing ID to `value` automatically loads the entity
* Background single query for label display
* Display only ID while loading

## displayField Options

### Specify by Field Name

The simplest way - specify the field name as a string.

```typescript theme={null}
<IdAsyncSelect
  config={UserAsyncIdConfig}
  subset="A"
  value={userId}
  onValueChange={setUserId}
  displayField="username"
/>
```

### Specify by Callback Function

Use a callback function when complex labels are needed.

```typescript theme={null}
<IdAsyncSelect
  config={EmployeeAsyncIdConfig}
  subset="A"
  value={employeeId}
  onValueChange={setEmployeeId}
  displayField={(row) =>
    `${row.employee_number} (Department: ${row.department?.name})`
  }
/>
```

### Auto Detection (Omit displayField)

When `displayField` is omitted, an appropriate field is automatically detected.

```typescript theme={null}
// Auto detection applied when displayField is omitted
<IdAsyncSelect
  config={UserAsyncIdConfig}
  subset="A"
  value={userId}
  onValueChange={setUserId}
/>
```

**Detection priority**:

1. name-like fields: `name`, `title`, `label`, `display_name`, `username`
2. First string type column (excluding id)
3. fallback: `id`

## Advanced Usage

### Pass Search Conditions with baseListParams

Set default search parameters.

```typescript theme={null}
<IdAsyncSelect
  config={UserAsyncIdConfig}
  subset="A"
  value={userId}
  onValueChange={setUserId}
  displayField="username"
  // Apply additional conditions on search
  baseListParams={{
    search: "username",
    role: "admin"
  }}
/>
```

### Get Full Row Data with onRowChange

When you need the full selected Row data, not just the ID:

```typescript theme={null}
function SelectWithRowData() {
  const [userId, setUserId] = useState<number | null>(null);
  const [selectedUser, setSelectedUser] = useState<User | undefined>();

  return (
    <IdAsyncSelect
      config={UserAsyncIdConfig}
      subset="A"
      value={userId}
      onValueChange={setUserId}
      onRowChange={(row) => setSelectedUser(row as User)}
      displayField="username"
    />
  );
}
```

### Change valueField

By default, the `id` field is used as the value, but you can use a different field.

```typescript theme={null}
<IdAsyncSelect
  config={UserAsyncIdConfig}
  subset="A"
  value={userUuid}
  onValueChange={setUserUuid}
  valueField="uuid"
  displayField="username"
/>
```

## Type Safety

### AsyncIdConfig Structure

The Config auto-generated from `services.generated.ts` has this structure:

```typescript theme={null}
// Auto-generated in services.generated.ts
export const UserAsyncIdConfig: AsyncIdConfig<
  "A" | "D", // TSubsetKey - Available Subset keys
  UserSubsetMapping, // TSubsetMapping - Subset type mapping
  UserListParams // TListParams - Search parameter types
> = {
  placeholderKey: "user.placeholder",
  useList: UserService.useList,
};
```

### Utilizing Type Inference

Types are automatically inferred through Config:

```typescript theme={null}
<IdAsyncSelect
  config={UserAsyncIdConfig}
  subset="A"                    // Choose from "A" | "D"
  displayField="username"       // Auto-complete only UserSubsetA fields
  baseListParams={{ role: "admin" }}  // Auto-complete based on UserListParams
  value={userId}
  onValueChange={setUserId}
  onRowChange={(row) => {
    // row type is inferred as UserSubsetA
    console.log(row.username);
  }}
/>
```

## Practical Examples

### Post Creation Form

```typescript theme={null}
import { useState } from "react";
import { IdAsyncSelect } from "@sonamu-kit/react-components";
import {
  UserAsyncIdConfig,
  CategoryAsyncIdConfig,
  TagAsyncIdConfig
} from "@/services/services.generated";
import { postService } from "@/services/post.service";

function CreatePostForm() {
  const [title, setTitle] = useState("");
  const [content, setContent] = useState("");
  const [authorId, setAuthorId] = useState<number | null>(null);
  const [categoryId, setCategoryId] = useState<number | null>(null);
  const [tagIds, setTagIds] = useState<number[]>([]);

  async function handleSubmit() {
    if (!authorId || !categoryId) {
      alert("Please select author and category");
      return;
    }

    await postService.create({
      title,
      content,
      author_id: authorId,
      category_id: categoryId,
      tag_ids: tagIds,
    });
  }

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Title</label>
        <input
          value={title}
          onChange={(e) => setTitle(e.target.value)}
        />
      </div>

      <div>
        <label>Content</label>
        <textarea
          value={content}
          onChange={(e) => setContent(e.target.value)}
        />
      </div>

      {/* Author selection (single) */}
      <div>
        <label>Author *</label>
        <IdAsyncSelect
          config={UserAsyncIdConfig}
          subset="A"
          value={authorId}
          onValueChange={setAuthorId}
          displayField="username"
          placeholder="Select author..."
        />
      </div>

      {/* Category selection (single) */}
      <div>
        <label>Category *</label>
        <IdAsyncSelect
          config={CategoryAsyncIdConfig}
          subset="A"
          value={categoryId}
          onValueChange={setCategoryId}
          displayField="name"
          placeholder="Select category..."
        />
      </div>

      {/* Tag selection (multiple) */}
      <div>
        <label>Tags</label>
        <IdAsyncSelect
          config={TagAsyncIdConfig}
          subset="A"
          value={tagIds}
          onValueChange={setTagIds}
          displayField="name"
          multiple
          placeholder="Select tags..."
        />
      </div>

      <button type="submit">Create Post</button>
    </form>
  );
}
```

### Hierarchical Selection

Pattern of selecting child after parent.

```typescript theme={null}
import {
  DepartmentAsyncIdConfig,
  EmployeeAsyncIdConfig
} from "@/services/services.generated";

function EmployeeAssignForm() {
  const [departmentId, setDepartmentId] = useState<number | null>(null);
  const [employeeId, setEmployeeId] = useState<number | null>(null);

  return (
    <div>
      {/* Step 1: Department selection */}
      <div>
        <label>Department</label>
        <IdAsyncSelect
          config={DepartmentAsyncIdConfig}
          subset="A"
          value={departmentId}
          onValueChange={(id) => {
            setDepartmentId(id);
            setEmployeeId(null); // Reset employee when department changes
          }}
          displayField="name"
        />
      </div>

      {/* Step 2: Employee selection (only after department selected) */}
      {departmentId && (
        <div>
          <label>Employee</label>
          <IdAsyncSelect
            config={EmployeeAsyncIdConfig}
            subset="A"
            value={employeeId}
            onValueChange={setEmployeeId}
            displayField="employee_number"
            // Query only employees in selected department
            baseListParams={{ department_id: departmentId }}
          />
        </div>
      )}
    </div>
  );
}
```

### Using with Forms

Example using with Sonamu's `useForm`.

```typescript theme={null}
import { useForm } from "@sonamu-kit/react-components";
import { CompanyAsyncIdConfig } from "@/services/services.generated";

function CompanySelectForm() {
  const form = useForm({
    defaultValues: { company_id: null as number | null },
  });

  return (
    <IdAsyncSelect
      config={CompanyAsyncIdConfig}
      subset="A"
      displayField="name"
      {...form.register("company_id")}
      placeholder="Search for a company"
    />
  );
}
```

## Props Reference

| Prop             | Type                          | Required | Description                                |
| ---------------- | ----------------------------- | -------- | ------------------------------------------ |
| `config`         | `AsyncIdConfig`               | ✓        | Entity's AsyncIdConfig object              |
| `subset`         | `string`                      | ✓        | Subset key to query                        |
| `value`          | `TValue \| TValue[] \| null`  |          | Selected value                             |
| `onValueChange`  | `(value) => void`             |          | Value change callback                      |
| `onRowChange`    | `(row) => void`               |          | Row data change callback                   |
| `displayField`   | `string \| ((row) => string)` |          | Display field (auto-detected if omitted)   |
| `valueField`     | `string`                      |          | Value field (default: "id")                |
| `baseListParams` | `object`                      |          | Additional parameters for search           |
| `preload`        | `boolean`                     |          | Load full list on mount (default: `false`) |
| `searchable`     | `boolean`                     |          | Show search filter in dropdown             |
| `multiple`       | `boolean`                     |          | Multiple selection mode                    |
| `placeholder`    | `string`                      |          | Placeholder text                           |
| `clearable`      | `boolean`                     |          | Whether selection can be cleared           |
| `disabled`       | `boolean`                     |          | Disabled state                             |
| `className`      | `string`                      |          | Additional CSS class                       |

### `preload` and `searchable` Combinations

The component's rendering mode changes depending on `preload` and `searchable` values:

| `preload` | `searchable` | Behavior                                                                   |
| --------- | ------------ | -------------------------------------------------------------------------- |
| `false`   | unset        | **Default async mode**: searches on the server as the user types a keyword |
| `true`    | unset        | **Sync dropdown mode**: loads full list on mount, no search input shown    |
| `true`    | `true`       | **Hybrid mode**: loads full list on mount + shows server search filter     |

<Info>
  When `baseListParams` contains meaningful filter values, the component also behaves in sync dropdown mode, the same as `preload`.
</Info>

## Cautions

<Warning>
  **Cautions when using ID Async Select**: 1. `config` should use objects auto-generated from
  `services.generated.ts` 2. `subset` must be a valid Subset key defined in the Entity 3. Verify
  **array type** for multiple selection 4. Field specified in `displayField` must be included in the
  Subset 5. Error handling required for initial value loading failure
</Warning>

## Next Steps

<CardGroup cols={2}>
  <Card title="View Scaffolding" icon="wand-magic-sparkles" href="/en/frontend-integration/react-components/scaffolding-views">
    Auto view generation
  </Card>

  <Card title="Search Input" icon="magnifying-glass" href="/en/frontend-integration/react-components/search-input">
    Search component
  </Card>

  <Card title="Custom Components" icon="sliders" href="/en/frontend-integration/react-components/custom-components">
    Component customization
  </Card>

  <Card title="Using Services" icon="code" href="/en/frontend-integration/generated-services/using-services">
    Service integration
  </Card>
</CardGroup>
