feat(form): Add controlled mode to Select component
Refactors the `Select` component to support being used as a controlled component by accepting a `value` prop. This allows its state to be managed externally, for example, by React Hook Form. This change was necessary to properly implement the `clearable` functionality within forms. Previously, clearing the select would only update its internal state, not the form's state. - Added `value` and `useEffect` to sync with external state. - Updated the `onClear` handler to propagate the change via `onChange`. - Modified `AdminListFilters` to use the `Select` component in a controlled manner, passing `watch` and `setValue` from React Hook Form. - Adjusted the filter layout to be more responsive.
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
import { ChevronUpIcon } from "@/assets/icons";
|
import { ChevronUpIcon } from "@/assets/icons";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { ILabelValue } from "@/types/shared";
|
import { ILabelValue } from "@/types/shared";
|
||||||
import { useId, useState, type ChangeEvent } from "react";
|
import { useEffect, useId, useState, type ChangeEvent } from "react";
|
||||||
import {
|
import {
|
||||||
type FieldErrors,
|
type FieldErrors,
|
||||||
type FieldValues,
|
type FieldValues,
|
||||||
@@ -24,8 +24,9 @@ type PropsType<T extends FieldValues> = {
|
|||||||
required?: boolean;
|
required?: boolean;
|
||||||
clearable?: boolean;
|
clearable?: boolean;
|
||||||
onClear?: () => void;
|
onClear?: () => void;
|
||||||
|
value?: string;
|
||||||
} & (
|
} & (
|
||||||
| { placeholder?: string; defaultValue: string }
|
| { placeholder?: string; defaultValue?: string }
|
||||||
| { placeholder: string; defaultValue?: string }
|
| { placeholder: string; defaultValue?: string }
|
||||||
) &
|
) &
|
||||||
Partial<UseFormRegisterReturn>;
|
Partial<UseFormRegisterReturn>;
|
||||||
@@ -46,6 +47,7 @@ export function Select<T extends FieldValues>({
|
|||||||
ref,
|
ref,
|
||||||
clearable = false,
|
clearable = false,
|
||||||
onClear,
|
onClear,
|
||||||
|
value: controlledValue,
|
||||||
...props
|
...props
|
||||||
}: PropsType<T>) {
|
}: PropsType<T>) {
|
||||||
const id = useId();
|
const id = useId();
|
||||||
@@ -54,10 +56,23 @@ export function Select<T extends FieldValues>({
|
|||||||
const [value, setValue] = useState(defaultValue || "");
|
const [value, setValue] = useState(defaultValue || "");
|
||||||
const error = errors && errors[name];
|
const error = errors && errors[name];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (controlledValue !== undefined) {
|
||||||
|
setValue(controlledValue);
|
||||||
|
setIsOptionSelected(!!controlledValue);
|
||||||
|
}
|
||||||
|
}, [controlledValue]);
|
||||||
|
|
||||||
const handleClear = () => {
|
const handleClear = () => {
|
||||||
setValue("");
|
setValue("");
|
||||||
setIsOptionSelected(false);
|
setIsOptionSelected(false);
|
||||||
onClear?.();
|
onClear?.();
|
||||||
|
if (onChange) {
|
||||||
|
const event = {
|
||||||
|
target: { value: "" },
|
||||||
|
} as ChangeEvent<HTMLSelectElement>;
|
||||||
|
onChange(event);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -83,7 +98,9 @@ export function Select<T extends FieldValues>({
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
if (controlledValue === undefined) {
|
||||||
setValue(e.target.value);
|
setValue(e.target.value);
|
||||||
|
}
|
||||||
setIsOptionSelected(!!e.target.value);
|
setIsOptionSelected(!!e.target.value);
|
||||||
onChange?.(e);
|
onChange?.(e);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import InputGroup from "@/components/FormElements/InputGroup";
|
import InputGroup from "@/components/FormElements/InputGroup";
|
||||||
import { Select } from "@/components/FormElements/select";
|
import { Select } from "@/components/FormElements/select";
|
||||||
import { AdminRoles, AdminStatus } from "@/constants/enums";
|
import { AdminStatus } from "@/constants/enums";
|
||||||
import { getEnumAsArray } from "@/utils/shared";
|
import { getEnumAsArray } from "@/utils/shared";
|
||||||
import {
|
import {
|
||||||
FieldValues,
|
FieldValues,
|
||||||
UseFormHandleSubmit,
|
UseFormHandleSubmit,
|
||||||
UseFormRegister,
|
UseFormRegister,
|
||||||
|
UseFormSetValue,
|
||||||
|
UseFormWatch,
|
||||||
} from "react-hook-form";
|
} from "react-hook-form";
|
||||||
|
|
||||||
interface AdminListFiltersProps {
|
interface AdminListFiltersProps {
|
||||||
@@ -14,6 +16,8 @@ interface AdminListFiltersProps {
|
|||||||
isMutating: number;
|
isMutating: number;
|
||||||
handleFilterSubmit: UseFormHandleSubmit<FieldValues>;
|
handleFilterSubmit: UseFormHandleSubmit<FieldValues>;
|
||||||
search: (data: any) => void;
|
search: (data: any) => void;
|
||||||
|
watch: UseFormWatch<FieldValues>;
|
||||||
|
setValue: UseFormSetValue<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AdminListFilters = ({
|
const AdminListFilters = ({
|
||||||
@@ -22,10 +26,12 @@ const AdminListFilters = ({
|
|||||||
isMutating,
|
isMutating,
|
||||||
handleFilterSubmit,
|
handleFilterSubmit,
|
||||||
search,
|
search,
|
||||||
|
watch,
|
||||||
|
setValue,
|
||||||
}: AdminListFiltersProps) => {
|
}: AdminListFiltersProps) => {
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
className="grid !min-w-full grid-cols-3 gap-4"
|
className="grid !min-w-full grid-cols-1 gap-4 xl:grid-cols-2"
|
||||||
onSubmit={handleFilterSubmit(search)}
|
onSubmit={handleFilterSubmit(search)}
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
@@ -35,6 +41,8 @@ const AdminListFilters = ({
|
|||||||
name="status"
|
name="status"
|
||||||
placeholder="Status"
|
placeholder="Status"
|
||||||
clearable
|
clearable
|
||||||
|
value={watch("status")}
|
||||||
|
onClear={() => setValue("status", undefined)}
|
||||||
/>
|
/>
|
||||||
<InputGroup
|
<InputGroup
|
||||||
{...filterRegister("search")}
|
{...filterRegister("search")}
|
||||||
@@ -50,13 +58,16 @@ const AdminListFilters = ({
|
|||||||
placeholder="email"
|
placeholder="email"
|
||||||
type="email"
|
type="email"
|
||||||
/> */}
|
/> */}
|
||||||
<Select
|
{/* <Select
|
||||||
{...filterRegister("role")}
|
{...filterRegister("role")}
|
||||||
name="role"
|
name="role"
|
||||||
items={getEnumAsArray(AdminRoles)}
|
items={getEnumAsArray(AdminRoles)}
|
||||||
label="Role"
|
label="Role"
|
||||||
placeholder="Role"
|
placeholder="Role"
|
||||||
/>
|
clearable
|
||||||
|
value={watch("role")}
|
||||||
|
onClear={() => setValue("role", undefined)}
|
||||||
|
/> */}
|
||||||
{/* <InputGroup
|
{/* <InputGroup
|
||||||
{...filterRegister("username")}
|
{...filterRegister("username")}
|
||||||
label="username"
|
label="username"
|
||||||
@@ -71,6 +82,7 @@ const AdminListFilters = ({
|
|||||||
placeholder="full name"
|
placeholder="full name"
|
||||||
type="text"
|
type="text"
|
||||||
/> */}
|
/> */}
|
||||||
|
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
|
|||||||
@@ -39,10 +39,12 @@ const AdminsTable = () => {
|
|||||||
currentUser,
|
currentUser,
|
||||||
setStatus,
|
setStatus,
|
||||||
status,
|
status,
|
||||||
|
setFilterValue,
|
||||||
handleFilterSubmit,
|
handleFilterSubmit,
|
||||||
search,
|
search,
|
||||||
filterRegister,
|
filterRegister,
|
||||||
isMutating,
|
isMutating,
|
||||||
|
watch,
|
||||||
} = useAdminsPresenter();
|
} = useAdminsPresenter();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -85,9 +87,7 @@ const AdminsTable = () => {
|
|||||||
<TableCell className="text-start" colSpan={100}>
|
<TableCell className="text-start" colSpan={100}>
|
||||||
{item?.email}
|
{item?.email}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-start" colSpan={100}>
|
|
||||||
{item?.role}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="text-start" colSpan={100}>
|
<TableCell className="text-start" colSpan={100}>
|
||||||
{item?.username}
|
{item?.username}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
@@ -162,15 +162,17 @@ const AdminsTable = () => {
|
|||||||
<Modal
|
<Modal
|
||||||
isOpen={isFilterModalOpen}
|
isOpen={isFilterModalOpen}
|
||||||
onClose={() => setIsFilterModalOpen(false)}
|
onClose={() => setIsFilterModalOpen(false)}
|
||||||
classNames="w-3/4"
|
classNames="w-full xl:w-1/2"
|
||||||
showButtons={false}
|
showButtons={false}
|
||||||
>
|
>
|
||||||
<AdminListFilters
|
<AdminListFilters
|
||||||
|
setValue={setFilterValue}
|
||||||
filterRegister={filterRegister}
|
filterRegister={filterRegister}
|
||||||
handleFilterSubmit={handleFilterSubmit}
|
handleFilterSubmit={handleFilterSubmit}
|
||||||
isMutating={isMutating as number}
|
isMutating={isMutating as number}
|
||||||
search={search}
|
search={search}
|
||||||
setIsFilterModalOpen={setIsFilterModalOpen}
|
setIsFilterModalOpen={setIsFilterModalOpen}
|
||||||
|
watch={watch}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
{isModalOpen && (
|
{isModalOpen && (
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ export const useAdminsPresenter = () => {
|
|||||||
register: filterRegister,
|
register: filterRegister,
|
||||||
handleSubmit: handleFilterSubmit,
|
handleSubmit: handleFilterSubmit,
|
||||||
reset: filterFormReset,
|
reset: filterFormReset,
|
||||||
|
watch,
|
||||||
|
setValue: setFilterValue,
|
||||||
} = useForm();
|
} = useForm();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -86,13 +88,14 @@ export const useAdminsPresenter = () => {
|
|||||||
"row",
|
"row",
|
||||||
"full name",
|
"full name",
|
||||||
"email",
|
"email",
|
||||||
"role",
|
|
||||||
"username",
|
"username",
|
||||||
"status",
|
"status",
|
||||||
"actions",
|
"actions",
|
||||||
];
|
];
|
||||||
return {
|
return {
|
||||||
allUsers,
|
allUsers,
|
||||||
|
setFilterValue,
|
||||||
metadata,
|
metadata,
|
||||||
columns,
|
columns,
|
||||||
isPending,
|
isPending,
|
||||||
@@ -118,5 +121,6 @@ export const useAdminsPresenter = () => {
|
|||||||
filterRegister,
|
filterRegister,
|
||||||
handleFilterSubmit,
|
handleFilterSubmit,
|
||||||
search,
|
search,
|
||||||
|
watch,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user