useSelectionμ λͺ©λ‘ νμ΄μ§μμ 체ν¬λ°μ€λ₯Ό ν΅ν λ€μ€ μ νμ κ΄λ¦¬νλ ν
μ
λλ€. Shift+ν΄λ¦ λ²μ μ ν, μ 체 μ ν/ν΄μ λ±μ κΈ°λ₯μ μ 곡ν©λλ€.
ν΅μ¬ κΈ°λ₯
λ€μ€ μ ν
κ°λ³/μ 체 μ ν κ΄λ¦¬Set κΈ°λ° ν¨μ¨μ μν
λ²μ μ ν
Shift+ν΄λ¦ μ§μμ°μλ νλͺ© νλ²μ μ ν
μλ κ²μ¦
νμ΄μ§ λ³κ²½ μ μλ μ 리쑴μ¬νλ νλͺ©λ§ μ μ§
νμ
μμ
μ λ€λ¦ νμ
μ§μany νμ
ν€ μ¬μ© κ°λ₯
κΈ°λ³Έ μ¬μ©λ²
Import λ° μ΄κΈ°ν
import { useSelection } from "@sonamu-kit/react-components";
export function UserListPage() {
const { data } = UserService.useUsers("A", listParams);
const allIds = data?.rows.map((row) => row.id) ?? [];
// useSelection μ΄κΈ°ν
const {
selectedKeys, // μ νλ ν€ λ°°μ΄
getSelected, // νΉμ ν€κ° μ νλμλμ§ νμΈ
toggle, // κ°λ³ νλͺ© μ ν/ν΄μ ν κΈ
selectAll, // μ 체 μ ν
deselectAll, // μ 체 ν΄μ
isAllSelected, // μ 체 μ ν μ¬λΆ
handleCheckboxClick, // Shift+ν΄λ¦ λ²μ μ ν
} = useSelection(allIds, []); // allIds: μ 체 ν€, []: μ΄κΈ° μ ν
// ...
}
νλΌλ―Έν° μ€λͺ
:
allIds: νμ¬ νμ΄μ§μ λͺ¨λ ν€ λ°°μ΄ (μ: [1, 2, 3, 4, 5])
defaultSelectedKeys: μ΄κΈ° μ ν μν (μ ν μ¬ν, κΈ°λ³Έκ°: [])
κ°λ³ νλͺ© μ ν
import { Checkbox } from "@sonamu-kit/react-components/components";
const { getSelected, toggle } = useSelection(allIds, []);
<Checkbox
checked={getSelected(row.id)}
onValueChange={() => toggle(row.id)}
/>
λμ:
getSelected(key): ν΄λΉ ν€κ° μ νλμμΌλ©΄ true, μλλ©΄ false
toggle(key): μ ν μνλ₯Ό λ°λλ‘ λ³κ²½
μ 체 μ ν/ν΄μ
const { isAllSelected, selectAll, deselectAll } = useSelection(allIds, []);
<Checkbox
checked={isAllSelected}
onValueChange={(checked) => {
if (checked) {
selectAll();
} else {
deselectAll();
}
}}
/>
λμ:
isAllSelected: νμ¬ νμ΄μ§μ λͺ¨λ νλͺ©μ΄ μ νλμλμ§
selectAll(): νμ¬ νμ΄μ§μ λͺ¨λ νλͺ© μ ν
deselectAll(): λͺ¨λ μ ν ν΄μ
Shift+ν΄λ¦ λ²μ μ ν
const { handleCheckboxClick } = useSelection(allIds, []);
{rows.map((row, index) => (
<tr key={row.id}>
<td onClick={(e) => handleCheckboxClick(e, index)}>
<Checkbox
checked={getSelected(row.id)}
onValueChange={() => toggle(row.id)}
/>
</td>
{/* ... */}
</tr>
))}
λμ:
- μΌλ° ν΄λ¦: κ°λ³ νλͺ© μ ν/ν΄μ
- Shift+ν΄λ¦: λ§μ§λ§ μ ν μμΉλΆν° νμ¬ μμΉκΉμ§ λͺ¨λ μ ν
Shift+ν΄λ¦ λμ μ리handleCheckboxClickμ λ§μ§λ§ ν΄λ¦ μΈλ±μ€λ₯Ό κΈ°μ΅νκ³ , Shift+ν΄λ¦ μ κ·Έ μ¬μ΄μ λͺ¨λ νλͺ©μ μ νν©λλ€.νλͺ©: [A, B, C, D, E]
1. A ν΄λ¦ β A μ ν
2. Dμ Shift+ν΄λ¦ β A, B, C, D λͺ¨λ μ ν
μ€μ μμ
μμ ν ν
μ΄λΈ ꡬν
import { useSelection } from "@sonamu-kit/react-components";
import { Checkbox, Button, Table } from "@sonamu-kit/react-components/components";
import { UserService } from "@/services/services.generated";
export function UserListPage() {
const { data } = UserService.useUsers("A", listParams);
const { rows } = data ?? {};
const allIds = rows?.map(row => row.id) ?? [];
const {
selectedKeys,
getSelected,
toggle,
selectAll,
deselectAll,
isAllSelected,
handleCheckboxClick,
} = useSelection(allIds, []);
// λ²ν¬ μμ
const handleBulkDelete = async () => {
if (selectedKeys.length === 0) {
alert("μμ ν νλͺ©μ μ ννμΈμ");
return;
}
if (confirm(`${selectedKeys.length}κ° νλͺ©μ μμ νμκ² μ΅λκΉ?`)) {
await UserService.del(selectedKeys);
deselectAll(); // μμ ν μ ν ν΄μ
}
};
return (
<div>
{/* λ²ν¬ μμ
λ²νΌ */}
{selectedKeys.length > 0 && (
<div className="mb-4 flex gap-2">
<span>{selectedKeys.length}κ° μ νλ¨</span>
<Button variant="red" onClick={handleBulkDelete}>
μ ν νλͺ© μμ
</Button>
</div>
)}
<Table>
<thead>
<tr>
{/* μ 체 μ ν 체ν¬λ°μ€ */}
<th className="w-[40px]">
<Checkbox
checked={isAllSelected}
onValueChange={(checked) => {
if (checked) {
selectAll();
} else {
deselectAll();
}
}}
/>
</th>
<th>ID</th>
<th>μ΄λ¦</th>
<th>μ΄λ©μΌ</th>
</tr>
</thead>
<tbody>
{rows?.map((row, index) => (
<tr key={row.id}>
{/* κ°λ³ μ ν 체ν¬λ°μ€ (Shift+ν΄λ¦ μ§μ) */}
<td onClick={(e) => handleCheckboxClick(e, index)}>
<Checkbox
checked={getSelected(row.id)}
onValueChange={() => toggle(row.id)}
/>
</td>
<td>{row.id}</td>
<td>{row.username}</td>
<td>{row.email}</td>
</tr>
))}
</tbody>
</Table>
</div>
);
}
νμ΄μ§ λ³κ²½ μ μλ κ²μ¦
useSelectionμ allIdsκ° λ³κ²½λλ©΄ μλμΌλ‘ μ ν μνλ₯Ό κ²μ¦ν©λλ€.
// 1νμ΄μ§: rows = [1, 2, 3]
const selection1 = useSelection([1, 2, 3], []);
selection1.toggle(1); // 1 μ ν
selection1.toggle(2); // 2 μ ν
// selectedKeys = [1, 2]
// 2νμ΄μ§λ‘ μ΄λ: rows = [4, 5, 6]
const selection2 = useSelection([4, 5, 6], []);
// selectedKeysλ μλμΌλ‘ []λ‘ μ λ¦¬λ¨ (1, 2κ° allIdsμ μμ)
κ²μ¦ λ‘μ§:
// useSelection λ΄λΆ (list-helpers.ts:84-91)
useEffect(() => {
const selectionKeys = Array.from(selection.keys());
const validKeys = intersection(allKeys, selectionKeys);
// λ³κ²½μ΄ νμν κ²½μ°μλ§ μν μ
λ°μ΄νΈ
if (validKeys.length !== selectionKeys.length) {
setSelection(new Map(validKeys.map((key) => [key, true])));
}
}, [allKeys, selection]);
μ μλ κ²μ¦μ΄ νμνκ°μ?νμ΄μ§λ₯Ό λκΈ°κ±°λ νν°λ₯Ό λ³κ²½νλ©΄ rowsκ° λ°λλλ€. μ΄μ νμ΄μ§μμ μ νν νλͺ©μ΄ μ νμ΄μ§μ μμ μ μμΌλ―λ‘, μλμΌλ‘ μ ν¨νμ§ μμ μ νμ μ κ±°ν©λλ€.
μ νλ νλͺ©μΌλ‘ μμ
const { selectedKeys } = useSelection(allIds, []);
// μ νλ νλͺ©μ μ 보 κ°μ Έμ€κΈ°
const selectedRows = rows?.filter((row) => selectedKeys.includes(row.id)) ?? [];
// λ²ν¬ μμ
const handleBulkUpdate = async () => {
await UserService.save({
params: selectedRows.map((row) => ({
id: row.id,
status: "active",
})),
});
};
// λ²ν¬ λ΄λ³΄λ΄κΈ°
const handleBulkExport = () => {
const csv = selectedRows.map((row) => `${row.id},${row.username},${row.email}`).join("\n");
// CSV λ€μ΄λ‘λ
const blob = new Blob([csv], { type: "text/csv" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "users.csv";
a.click();
};
κ³ κΈ μ¬μ©
μ΄κΈ° μ ν μν
// URL νλΌλ―Έν°μμ μ ν μν 볡μ
const searchParams = new URLSearchParams(window.location.search);
const preselectedIds = searchParams.get("selected")?.split(",").map(Number) ?? [];
const { selectedKeys } = useSelection(allIds, preselectedIds);
μ ν μν μ μ₯
const { selectedKeys } = useSelection(allIds, []);
// μ ν μνλ₯Ό localStorageμ μ μ₯
useEffect(() => {
localStorage.setItem("selectedUserIds", JSON.stringify(selectedKeys));
}, [selectedKeys]);
// λ€μ λ°©λ¬Έ μ 볡μ
const savedIds = JSON.parse(localStorage.getItem("selectedUserIds") ?? "[]");
const { selectedKeys } = useSelection(allIds, savedIds);
μ‘°κ±΄λΆ μ ν μ ν
const { toggle } = useSelection(allIds, []);
// νΉμ 쑰건μ νλͺ©λ§ μ ν κ°λ₯
const handleToggle = (row: UserRow) => {
if (row.role === "admin") {
alert("κ΄λ¦¬μλ μ νν μ μμ΅λλ€");
return;
}
toggle(row.id);
};
<Checkbox
checked={getSelected(row.id)}
onValueChange={() => handleToggle(row)}
disabled={row.role === "admin"}
/>
λ΄λΆ ꡬ쑰
useSelectionμ Map<T, boolean> κ΅¬μ‘°λ‘ μ ν μνλ₯Ό κ΄λ¦¬ν©λλ€.
// λ΄λΆ μν (λ¨μν)
const [selection, setSelection] = useState<Map<number, boolean>>(
new Map([
[1, true], // ID 1 μ νλ¨
[2, false], // ID 2 μ ν μ λ¨
[3, true], // ID 3 μ νλ¨
]),
);
// selectedKeys μΆμΆ
const selectedKeys = Array.from(selection)
.filter(([key, value]) => value === true)
.map(([key]) => key);
// κ²°κ³Ό: [1, 3]
Mapμ μ¬μ©νλ μ΄μ :
- O(1) μκ° λ³΅μ‘λλ‘ λΉ λ₯Έ μ‘°ν
- ν€μ νμ
μ μ½ μμ (number, string, object λͺ¨λ κ°λ₯)
- Setλ³΄λ€ νμ₯μ± λμ (μΆκ° λ©νλ°μ΄ν° μ μ₯ κ°λ₯)
νμ
μμ μ±
useSelectionμ μ λ€λ¦μΌλ‘ ν€ νμ
μ μ§μ ν μ μμ΅λλ€.
// number νμ
const selection1 = useSelection<number>([1, 2, 3], []);
selection1.toggle(1); // β
OK
selection1.toggle("1"); // β νμ
μλ¬
// string νμ
const selection2 = useSelection<string>(["a", "b", "c"], []);
selection2.toggle("a"); // β
OK
selection2.toggle(1); // β νμ
μλ¬
// 볡μ‘ν κ°μ²΄ νμ
type UserKey = { id: number; email: string };
const keys: UserKey[] = [
{ id: 1, email: "a@example.com" },
{ id: 2, email: "b@example.com" },
];
const selection3 = useSelection<UserKey>(keys, []);
μ£Όμμ¬ν
1. allIdsλ λ°λμ μ 곡
// β μλͺ»λ μ¬μ©
const { selectedKeys } = useSelection([], []); // λΉ λ°°μ΄
// β
μ¬λ°λ₯Έ μ¬μ©
const allIds = rows?.map((row) => row.id) ?? [];
const { selectedKeys } = useSelection(allIds, []);
2. νμ΄μ§ λ³κ²½ μ μ ν μ μ§νκ³ μΆλ€λ©΄
κΈ°λ³Έμ μΌλ‘ νμ΄μ§λ₯Ό λκΈ°λ©΄ μ νμ΄ μ΄κΈ°νλ©λλ€. μ μ§νλ €λ©΄ λ³λ κ΄λ¦¬κ° νμν©λλ€.
// νμ΄μ§ μ 체 μ ν μν (useStateλ‘ λ³λ κ΄λ¦¬)
const [globalSelectedIds, setGlobalSelectedIds] = useState<Set<number>>(new Set());
// νμ¬ νμ΄μ§ μ νκ³Ό λκΈ°ν
useEffect(() => {
const currentPageSelected = allIds.filter((id) => globalSelectedIds.has(id));
// useSelectionμ μνμ λκΈ°ν
}, [allIds, globalSelectedIds]);
3. handleCheckboxClick μ¬μ© μ μ£Όμ
handleCheckboxClickμ <td>λ 체ν¬λ°μ€ 컨ν
μ΄λμ μ°κ²°ν΄μΌ ν©λλ€.
// β
μ¬λ°λ₯Έ μ¬μ©
<td onClick={(e) => handleCheckboxClick(e, index)}>
<Checkbox ... />
</td>
// β μλͺ»λ μ¬μ© (ν΄λ¦ μ΄λ²€νΈκ° μ νλμ§ μμ)
<Checkbox
onClick={(e) => handleCheckboxClick(e, index)}
...
/>
κ΄λ ¨ λ¬Έμ