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 { cn } from "@/lib/utils";
|
||||
import { ILabelValue } from "@/types/shared";
|
||||
import { useId, useState, type ChangeEvent } from "react";
|
||||
import { useEffect, useId, useState, type ChangeEvent } from "react";
|
||||
import {
|
||||
type FieldErrors,
|
||||
type FieldValues,
|
||||
@@ -24,8 +24,9 @@ type PropsType<T extends FieldValues> = {
|
||||
required?: boolean;
|
||||
clearable?: boolean;
|
||||
onClear?: () => void;
|
||||
value?: string;
|
||||
} & (
|
||||
| { placeholder?: string; defaultValue: string }
|
||||
| { placeholder?: string; defaultValue?: string }
|
||||
| { placeholder: string; defaultValue?: string }
|
||||
) &
|
||||
Partial<UseFormRegisterReturn>;
|
||||
@@ -46,6 +47,7 @@ export function Select<T extends FieldValues>({
|
||||
ref,
|
||||
clearable = false,
|
||||
onClear,
|
||||
value: controlledValue,
|
||||
...props
|
||||
}: PropsType<T>) {
|
||||
const id = useId();
|
||||
@@ -54,10 +56,23 @@ export function Select<T extends FieldValues>({
|
||||
const [value, setValue] = useState(defaultValue || "");
|
||||
const error = errors && errors[name];
|
||||
|
||||
useEffect(() => {
|
||||
if (controlledValue !== undefined) {
|
||||
setValue(controlledValue);
|
||||
setIsOptionSelected(!!controlledValue);
|
||||
}
|
||||
}, [controlledValue]);
|
||||
|
||||
const handleClear = () => {
|
||||
setValue("");
|
||||
setIsOptionSelected(false);
|
||||
onClear?.();
|
||||
if (onChange) {
|
||||
const event = {
|
||||
target: { value: "" },
|
||||
} as ChangeEvent<HTMLSelectElement>;
|
||||
onChange(event);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -83,7 +98,9 @@ export function Select<T extends FieldValues>({
|
||||
ref={ref}
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
setValue(e.target.value);
|
||||
if (controlledValue === undefined) {
|
||||
setValue(e.target.value);
|
||||
}
|
||||
setIsOptionSelected(!!e.target.value);
|
||||
onChange?.(e);
|
||||
}}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import InputGroup from "@/components/FormElements/InputGroup";
|
||||
import { Select } from "@/components/FormElements/select";
|
||||
import { AdminRoles, AdminStatus } from "@/constants/enums";
|
||||
import { AdminStatus } from "@/constants/enums";
|
||||
import { getEnumAsArray } from "@/utils/shared";
|
||||
import {
|
||||
FieldValues,
|
||||
UseFormHandleSubmit,
|
||||
UseFormRegister,
|
||||
UseFormSetValue,
|
||||
UseFormWatch,
|
||||
} from "react-hook-form";
|
||||
|
||||
interface AdminListFiltersProps {
|
||||
@@ -14,6 +16,8 @@ interface AdminListFiltersProps {
|
||||
isMutating: number;
|
||||
handleFilterSubmit: UseFormHandleSubmit<FieldValues>;
|
||||
search: (data: any) => void;
|
||||
watch: UseFormWatch<FieldValues>;
|
||||
setValue: UseFormSetValue<any>;
|
||||
}
|
||||
|
||||
const AdminListFilters = ({
|
||||
@@ -22,10 +26,12 @@ const AdminListFilters = ({
|
||||
isMutating,
|
||||
handleFilterSubmit,
|
||||
search,
|
||||
watch,
|
||||
setValue,
|
||||
}: AdminListFiltersProps) => {
|
||||
return (
|
||||
<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)}
|
||||
>
|
||||
<Select
|
||||
@@ -35,6 +41,8 @@ const AdminListFilters = ({
|
||||
name="status"
|
||||
placeholder="Status"
|
||||
clearable
|
||||
value={watch("status")}
|
||||
onClear={() => setValue("status", undefined)}
|
||||
/>
|
||||
<InputGroup
|
||||
{...filterRegister("search")}
|
||||
@@ -50,13 +58,16 @@ const AdminListFilters = ({
|
||||
placeholder="email"
|
||||
type="email"
|
||||
/> */}
|
||||
<Select
|
||||
{/* <Select
|
||||
{...filterRegister("role")}
|
||||
name="role"
|
||||
items={getEnumAsArray(AdminRoles)}
|
||||
label="Role"
|
||||
placeholder="Role"
|
||||
/>
|
||||
clearable
|
||||
value={watch("role")}
|
||||
onClear={() => setValue("role", undefined)}
|
||||
/> */}
|
||||
{/* <InputGroup
|
||||
{...filterRegister("username")}
|
||||
label="username"
|
||||
@@ -71,6 +82,7 @@ const AdminListFilters = ({
|
||||
placeholder="full name"
|
||||
type="text"
|
||||
/> */}
|
||||
|
||||
<div className="flex gap-4">
|
||||
<button
|
||||
type="submit"
|
||||
|
||||
@@ -39,10 +39,12 @@ const AdminsTable = () => {
|
||||
currentUser,
|
||||
setStatus,
|
||||
status,
|
||||
setFilterValue,
|
||||
handleFilterSubmit,
|
||||
search,
|
||||
filterRegister,
|
||||
isMutating,
|
||||
watch,
|
||||
} = useAdminsPresenter();
|
||||
|
||||
return (
|
||||
@@ -85,9 +87,7 @@ const AdminsTable = () => {
|
||||
<TableCell className="text-start" colSpan={100}>
|
||||
{item?.email}
|
||||
</TableCell>
|
||||
<TableCell className="text-start" colSpan={100}>
|
||||
{item?.role}
|
||||
</TableCell>
|
||||
|
||||
<TableCell className="text-start" colSpan={100}>
|
||||
{item?.username}
|
||||
</TableCell>
|
||||
@@ -162,15 +162,17 @@ const AdminsTable = () => {
|
||||
<Modal
|
||||
isOpen={isFilterModalOpen}
|
||||
onClose={() => setIsFilterModalOpen(false)}
|
||||
classNames="w-3/4"
|
||||
classNames="w-full xl:w-1/2"
|
||||
showButtons={false}
|
||||
>
|
||||
<AdminListFilters
|
||||
setValue={setFilterValue}
|
||||
filterRegister={filterRegister}
|
||||
handleFilterSubmit={handleFilterSubmit}
|
||||
isMutating={isMutating as number}
|
||||
search={search}
|
||||
setIsFilterModalOpen={setIsFilterModalOpen}
|
||||
watch={watch}
|
||||
/>
|
||||
</Modal>
|
||||
{isModalOpen && (
|
||||
|
||||
@@ -29,6 +29,8 @@ export const useAdminsPresenter = () => {
|
||||
register: filterRegister,
|
||||
handleSubmit: handleFilterSubmit,
|
||||
reset: filterFormReset,
|
||||
watch,
|
||||
setValue: setFilterValue,
|
||||
} = useForm();
|
||||
|
||||
const {
|
||||
@@ -86,13 +88,14 @@ export const useAdminsPresenter = () => {
|
||||
"row",
|
||||
"full name",
|
||||
"email",
|
||||
"role",
|
||||
|
||||
"username",
|
||||
"status",
|
||||
"actions",
|
||||
];
|
||||
return {
|
||||
allUsers,
|
||||
setFilterValue,
|
||||
metadata,
|
||||
columns,
|
||||
isPending,
|
||||
@@ -118,5 +121,6 @@ export const useAdminsPresenter = () => {
|
||||
filterRegister,
|
||||
handleFilterSubmit,
|
||||
search,
|
||||
watch,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user