μΈλν€ κ΄κ³λ₯Ό μ²λ¦¬νκΈ° μν ID Async Select μ»΄ν¬λνΈ μ¬μ©λ²μ μμλ΄
λλ€.
ID Async Select κ°μ
λΉλκΈ° λ‘λ©
API νΈμΆμλ μμ±
νμ
μμ
Entity κΈ°λ°ID νμ
보μ₯
κ²μ μ§μ
μ€μκ° νν°λ§λλ°μ΄μ€
λ€μ€ μ ν
λ¨μΌ/λ€μ€ λͺ¨λλ°°μ΄ λ°ν
ID Async Selectλ?
λ¬Έμ : μΈλν€ μ νμ 볡μ‘μ±
λ°μ΄ν°λ² μ΄μ€μμ μΈλν€ κ΄κ³λ₯Ό μ²λ¦¬ν λ, νλ‘ νΈμλμμλ κ΄λ ¨ μν°ν°λ₯Ό μ νν΄μΌ ν©λλ€.
μ ν΅μ μΈ λ°©μμ λ¬Έμ μ :
// β λͺ¨λ μ¬μ©μλ₯Ό ν λ²μ λ‘λ (λΉν¨μ¨μ )
const [users, setUsers] = useState([]);
useEffect(() => {
userService.list({ pageSize: 1000 }).then(({ users }) => {
setUsers(users); // 1000λͺ
μ λΆ λ‘λ!
});
}, []);
return (
<select>
{users.map((user) => (
<option key={user.id} value={user.id}>
{user.username}
</option>
))}
</select>
);
λ¬Έμ μ :
- μ±λ₯: λ°μ΄ν°κ° λ§μΌλ©΄ λ‘λ© μκ° μ¦κ°
- λ©λͺ¨λ¦¬: λΆνμν λ°μ΄ν°λ₯Ό λͺ¨λ λ©λͺ¨λ¦¬μ μ μ¬
- UX: μ¬μ©μκ° μνλ νλͺ©μ μ°ΎκΈ° μ΄λ €μ
- νμ₯μ±: λ°μ΄ν°κ° κ³μ λμ΄λλ©΄ κ°λΉ λΆκ°
ν΄κ²°: ID Async Select
Sonamuμ ID Async Selectλ νμν λλ§ λΉλκΈ°λ‘ λ‘λνκ³ κ²μ κΈ°λ₯μ μ 곡ν©λλ€.
// β
κ²μμ΄ κΈ°λ°μΌλ‘ νμν λ°μ΄ν°λ§ λ‘λ
import { IdAsyncSelect } from "@sonamu-kit/react-components";
import { UserAsyncIdConfig } from "@/services/services.generated";
<IdAsyncSelect
config={UserAsyncIdConfig}
subset="A"
value={selectedUserId}
onValueChange={setSelectedUserId}
/>
μ₯μ :
- β¨ μ±λ₯: μ²μμλ μ΅μνλ§ λ‘λ, κ²μ μ νμν κ²λ§
- β¨ μ¬μ©μ κ²½ν: μλμμ±μΌλ‘ λΉ λ₯Έ μ ν
- β¨ νμ
μμ : Entity νμ
μ΄ μλμΌλ‘ μ μ©
- β¨ νμ₯μ±: λ°μ΄ν°κ° μ무리 λ§μλ λ¬Έμ μμ
κΈ°λ³Έ μ¬μ©λ²
νμ Props
ID Async Selectλ λ€μ λ κ°μ§ ν΅μ¬ propsλ₯Ό μ¬μ©ν©λλ€:
config: services.generated.tsμμ μλ μμ±λ AsyncIdConfig κ°μ²΄
subset: μ‘°νν Subset ν€ (μ: βAβ, βDβ λ±)
import { IdAsyncSelect } from "@sonamu-kit/react-components";
import { UserAsyncIdConfig } from "@/services/services.generated";
<IdAsyncSelect
config={UserAsyncIdConfig}
subset="A"
value={userId}
onValueChange={setUserId}
/>
λ¨μΌ μ ν
νλμ μν°ν°λ₯Ό μ νν©λλ€.
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>
);
}
λμ λ°©μ:
- μ¬μ©μκ° νμ΄ννλ©΄ κ²μ API νΈμΆ
- κ²μ κ²°κ³Όλ₯Ό λλ‘λ€μ΄μ νμ
- μ ν μ IDλ§ μ μ₯ (λ©λͺ¨λ¦¬ ν¨μ¨μ )
λ€μ€ μ ν
μ¬λ¬ μν°ν°λ₯Ό μ νν©λλ€.
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>
);
}
λ€μ€ μ ν νΉμ§:
- μ νλ νλͺ©μ νκ·Έ ννλ‘ νμ
- X λ²νΌμΌλ‘ κ°λ³ μ κ±°
- λ°°μ΄ ννλ‘ ID λ°ν (
number[])
μ΄κΈ°κ° μ€μ
κΈ°μ‘΄ λ°μ΄ν° μμ μ μ΄κΈ°κ°μ μ€μ ν©λλ€.
function EditPostForm({ post }: { post: Post }) {
const [authorId, setAuthorId] = useState(post.author_id);
return (
<IdAsyncSelect
config={UserAsyncIdConfig}
subset="A"
value={authorId}
onValueChange={setAuthorId}
displayField="username"
/>
);
}
μ΄κΈ°κ° μ²λ¦¬:
valueμ IDλ₯Ό μ λ¬νλ©΄ μλμΌλ‘ ν΄λΉ μν°ν° λ‘λ
- λ μ΄λΈ νμλ₯Ό μν΄ λ°±κ·ΈλΌμ΄λμμ λ¨κ±΄ μ‘°ν
- λ‘λ© μ€μλ IDλ§ νμ
displayField μ΅μ
νλλͺ
μΌλ‘ μ§μ
κ°μ₯ κ°λ¨ν λ°©λ²μΌλ‘, νμν νλλͺ
μ λ¬Έμμ΄λ‘ μ§μ ν©λλ€.
<IdAsyncSelect
config={UserAsyncIdConfig}
subset="A"
value={userId}
onValueChange={setUserId}
displayField="username"
/>
μ½λ°± ν¨μλ‘ μ§μ
볡μ‘ν λ μ΄λΈμ΄ νμν κ²½μ° μ½λ°± ν¨μλ₯Ό μ¬μ©ν©λλ€.
<IdAsyncSelect
config={EmployeeAsyncIdConfig}
subset="A"
value={employeeId}
onValueChange={setEmployeeId}
displayField={(row) =>
`${row.employee_number} (μμ λΆμ: ${row.department?.name})`
}
/>
μλ νμ§ (displayField μλ΅)
displayFieldλ₯Ό μλ΅νλ©΄ μλμΌλ‘ μ μ ν νλλ₯Ό νμ§ν©λλ€.
// displayField μλ΅ μ μλ νμ§ μ μ©
<IdAsyncSelect
config={UserAsyncIdConfig}
subset="A"
value={userId}
onValueChange={setUserId}
/>
νμ§ μ°μ μμ:
- name-like νλ:
name, title, label, display_name, username
- 첫 λ²μ§Έ string νμ
μ»¬λΌ (id μ μΈ)
- fallback:
id
κ³ κΈ μ¬μ©λ²
baseListParamsλ‘ κ²μ 쑰건 μ λ¬
κΈ°λ³Έ κ²μ νλΌλ―Έν°λ₯Ό μ€μ ν μ μμ΅λλ€.
<IdAsyncSelect
config={UserAsyncIdConfig}
subset="A"
value={userId}
onValueChange={setUserId}
displayField="username"
// κ²μ μ μΆκ° 쑰건 μ μ©
baseListParams={{
search: "username",
role: "admin"
}}
/>
onRowChangeλ‘ μ 체 Row λ°μ΄ν° λ°κΈ°
IDλΏλ§ μλλΌ μ νλ Row μ 체 λ°μ΄ν°κ° νμν κ²½μ°:
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"
/>
);
}
valueField λ³κ²½
κΈ°λ³Έμ μΌλ‘ id νλλ₯Ό κ°μΌλ‘ μ¬μ©νμ§λ§, λ€λ₯Έ νλλ₯Ό μ¬μ©ν μλ μμ΅λλ€.
<IdAsyncSelect
config={UserAsyncIdConfig}
subset="A"
value={userUuid}
onValueChange={setUserUuid}
valueField="uuid"
displayField="username"
/>
νμ
μμ μ±
AsyncIdConfig ꡬ쑰
services.generated.tsμμ μλ μμ±λλ Configλ λ€μ ꡬ쑰λ₯Ό κ°μ΅λλ€:
// services.generated.tsμμ μλ μμ±
export const UserAsyncIdConfig: AsyncIdConfig<
"A" | "D", // TSubsetKey - μ¬μ© κ°λ₯ν Subset ν€
UserSubsetMapping, // TSubsetMapping - Subsetλ³ νμ
λ§€ν
UserListParams // TListParams - κ²μ νλΌλ―Έν° νμ
> = {
placeholderKey: "user.placeholder",
useList: UserService.useList,
};
νμ
μΆλ‘ νμ©
Configλ₯Ό ν΅ν΄ νμ
μ΄ μλμΌλ‘ μΆλ‘ λ©λλ€:
<IdAsyncSelect
config={UserAsyncIdConfig}
subset="A" // "A" | "D" μ€ μ ν
displayField="username" // UserSubsetAμ νλλ§ μλμμ±
baseListParams={{ role: "admin" }} // UserListParams κΈ°λ° μλμμ±
value={userId}
onValueChange={setUserId}
onRowChange={(row) => {
// rowμ νμ
μ΄ UserSubsetAλ‘ μΆλ‘ λ¨
console.log(row.username);
}}
/>
μ€μ μμ
κ²μκΈ μμ± νΌ
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>
{/* μμ±μ μ ν (λ¨μΌ) */}
<div>
<label>Author *</label>
<IdAsyncSelect
config={UserAsyncIdConfig}
subset="A"
value={authorId}
onValueChange={setAuthorId}
displayField="username"
placeholder="Select author..."
/>
</div>
{/* μΉ΄ν
κ³ λ¦¬ μ ν (λ¨μΌ) */}
<div>
<label>Category *</label>
<IdAsyncSelect
config={CategoryAsyncIdConfig}
subset="A"
value={categoryId}
onValueChange={setCategoryId}
displayField="name"
placeholder="Select category..."
/>
</div>
{/* νκ·Έ μ ν (λ€μ€) */}
<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>
);
}
κ³μΈ΅μ μ ν
λΆλͺ¨λ₯Ό μ νν ν μμμ μ ννλ ν¨ν΄μ
λλ€.
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>
{/* 1λ¨κ³: λΆμ μ ν */}
<div>
<label>Department</label>
<IdAsyncSelect
config={DepartmentAsyncIdConfig}
subset="A"
value={departmentId}
onValueChange={(id) => {
setDepartmentId(id);
setEmployeeId(null); // λΆμ λ³κ²½ μ μ§μ μ΄κΈ°ν
}}
displayField="name"
/>
</div>
{/* 2λ¨κ³: μ§μ μ ν (λΆμ μ ν νμλ§) */}
{departmentId && (
<div>
<label>Employee</label>
<IdAsyncSelect
config={EmployeeAsyncIdConfig}
subset="A"
value={employeeId}
onValueChange={setEmployeeId}
displayField="employee_number"
// μ νλ λΆμμ μν μ§μλ§ μ‘°ν
baseListParams={{ department_id: departmentId }}
/>
</div>
)}
</div>
);
}
νΌκ³Ό ν¨κ» μ¬μ©
Sonamuμ useFormκ³Ό ν¨κ» μ¬μ©νλ μμ μ
λλ€.
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="νμ¬λ₯Ό κ²μνμΈμ"
/>
);
}
Props λ νΌλ°μ€
| Prop | νμ
| νμ | μ€λͺ
|
|---|
config | AsyncIdConfig | β | Entityμ AsyncIdConfig κ°μ²΄ |
subset | string | β | μ‘°νν Subset ν€ |
value | TValue | TValue[] | null | | μ νλ κ° |
onValueChange | (value) => void | | κ° λ³κ²½ μ½λ°± |
onRowChange | (row) => void | | Row λ°μ΄ν° λ³κ²½ μ½λ°± |
displayField | string | ((row) => string) | | νμ νλ (μλ΅ μ μλ νμ§) |
valueField | string | | κ° νλ (κΈ°λ³Έ: βidβ) |
baseListParams | object | | κ²μ μ μΆκ° νλΌλ―Έν° |
multiple | boolean | | λ€μ€ μ ν λͺ¨λ |
placeholder | string | | νλ μ΄μ€νλ ν
μ€νΈ |
clearable | boolean | | μ ν ν΄μ κ°λ₯ μ¬λΆ |
disabled | boolean | | λΉνμ±ν μ¬λΆ |
className | string | | μΆκ° CSS ν΄λμ€ |
μ£Όμμ¬ν
ID Async Select μ¬μ© μ μ£Όμμ¬ν: 1. configλ services.generated.tsμμ μλ μμ±λ κ°μ²΄
μ¬μ© 2. subsetμ Entityμ μ μλ μ ν¨ν Subset ν€μ¬μΌ ν¨ 3. λ€μ€ μ ν μ λ°°μ΄ νμ
νμΈ 4.
displayFieldλ‘ μ§μ ν νλλ ν΄λΉ Subsetμ ν¬ν¨λμ΄ μμ΄μΌ ν¨ 5. μ΄κΈ°κ° λ‘λ© μ€ν¨ μ μλ¬ μ²λ¦¬
νμ
λ€μ λ¨κ³
View Scaffolding
μλ λ·° μμ±
Search Input
κ²μ μ»΄ν¬λνΈ
컀μ€ν
μ»΄ν¬λνΈ
μ»΄ν¬λνΈ μ»€μ€ν°λ§μ΄μ§
Service μ¬μ©νκΈ°
Service ν΅ν©