Skip to content

Commit 773b2f5

Browse files
committed
feat: react-hook-form 적용
1 parent 09ee2f7 commit 773b2f5

File tree

4 files changed

+111
-91
lines changed

4 files changed

+111
-91
lines changed

packages/shop/src/components/common/option_group_input.tsx

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { CircularProgress, FormControl, InputLabel, MenuItem, Select, TextField, Tooltip } from "@mui/material";
22
import { Suspense } from "@suspensive/react";
33
import * as React from "react";
4+
import { Control, Controller, FieldValues } from "react-hook-form";
45
import * as R from "remeda";
56

67
import { PriceDisplay } from "./price_display";
@@ -33,7 +34,8 @@ const SelectableOptionGroupInput: React.FC<{
3334
defaultValue?: string;
3435
disabled?: boolean;
3536
disabledReason?: string;
36-
}> = ({ language, optionGroup, options, defaultValue, disabled, disabledReason }) => {
37+
control: Control<FieldValues, unknown, FieldValues>;
38+
}> = ({ language, optionGroup, options, defaultValue, disabled, disabledReason, control }) => {
3739
const optionElements = options.map((option) => {
3840
const isOptionOutOfStock = R.isNumber(option.leftover_stock) && option.leftover_stock <= 0;
3941

@@ -54,9 +56,14 @@ const SelectableOptionGroupInput: React.FC<{
5456
const selectElement = (
5557
<FormControl fullWidth>
5658
<InputLabel id={`${optionGroup.id}label`}>{optionGroup.name}</InputLabel>
57-
<Select label={`${optionGroup.id}label`} name={optionGroup.id} defaultValue={defaultValue} disabled={disabled} required>
58-
{optionElements}
59-
</Select>
59+
<Controller
60+
control={control}
61+
name={optionGroup.id}
62+
rules={{ required: true }}
63+
disabled={disabled}
64+
defaultValue={defaultValue || ""}
65+
render={({ field }) => <Select label={`${optionGroup.id}label`} {...field} children={optionElements} />}
66+
/>
6067
</FormControl>
6168
);
6269

@@ -68,17 +75,19 @@ const CustomResponseOptionGroupInput: React.FC<{
6875
defaultValue?: string;
6976
disabled?: boolean;
7077
disabledReason?: string;
71-
}> = ({ optionGroup, defaultValue, disabled, disabledReason }) => {
72-
const pattern = ShopAPIUtil.getCustomResponsePattern(optionGroup)?.source;
73-
78+
control: Control<FieldValues, unknown, FieldValues>;
79+
}> = ({ optionGroup, defaultValue, disabled, disabledReason, control }) => {
7480
const textFieldElement = (
75-
<TextField
76-
label={optionGroup.name}
81+
<Controller
82+
control={control}
7783
name={optionGroup.id}
78-
required
79-
defaultValue={defaultValue}
84+
rules={{ pattern: ShopAPIUtil.getCustomResponsePattern(optionGroup), required: true }}
8085
disabled={disabled}
81-
slotProps={{ htmlInput: { pattern } }}
86+
defaultValue={defaultValue || ""}
87+
render={({ field, formState: { errors } }) => {
88+
const errorMessage: string | undefined = errors?.[optionGroup.id]?.message?.toString();
89+
return <TextField label={optionGroup.name} {...field} error={!!errors[optionGroup.id]} helperText={errorMessage} />;
90+
}}
8291
/>
8392
);
8493

@@ -93,9 +102,17 @@ export const OptionGroupInput: React.FC<{
93102
defaultValue?: string;
94103
disabled?: boolean;
95104
disabledReason?: string;
96-
}> = ({ language, optionGroup, options, defaultValue, disabled, disabledReason }) =>
105+
106+
control: Control<FieldValues, unknown, FieldValues>;
107+
}> = ({ language, optionGroup, options, defaultValue, disabled, disabledReason, control }) =>
97108
optionGroup.is_custom_response ? (
98-
<CustomResponseOptionGroupInput optionGroup={optionGroup} defaultValue={defaultValue} disabled={disabled} disabledReason={disabledReason} />
109+
<CustomResponseOptionGroupInput
110+
optionGroup={optionGroup}
111+
defaultValue={defaultValue}
112+
disabled={disabled}
113+
disabledReason={disabledReason}
114+
control={control}
115+
/>
99116
) : (
100117
<SelectableOptionGroupInput
101118
language={language || "ko"}
@@ -104,14 +121,16 @@ export const OptionGroupInput: React.FC<{
104121
defaultValue={defaultValue}
105122
disabled={disabled}
106123
disabledReason={disabledReason}
124+
control={control}
107125
/>
108126
);
109127

110128
export const OrderProductRelationOptionInput: React.FC<{
111129
optionRel: ShopSchemas.OrderProductItem["options"][number];
112130
disabled?: boolean;
113131
disabledReason?: string;
114-
}> = Suspense.with({ fallback: <CircularProgress /> }, ({ optionRel, disabled, disabledReason }) => {
132+
control: Control<FieldValues, unknown, FieldValues>;
133+
}> = Suspense.with({ fallback: <CircularProgress /> }, ({ optionRel, disabled, disabledReason, control }) => {
115134
const { language } = ShopHooks.useShopContext();
116135
let defaultValue: string | null = null;
117136
let guessedDisabledReason: string | undefined = undefined;
@@ -147,6 +166,7 @@ export const OrderProductRelationOptionInput: React.FC<{
147166
defaultValue={defaultValue || undefined}
148167
disabled={disabled || !ShopAPIUtil.isOrderProductOptionModifiable(optionRel)}
149168
disabledReason={disabledReason || guessedDisabledReason}
169+
control={control}
150170
/>
151171
);
152172
});

packages/shop/src/components/features/cart.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ErrorBoundary, Suspense } from "@suspensive/react";
44
import { useQueryClient } from "@tanstack/react-query";
55
import { enqueueSnackbar, OptionsObject } from "notistack";
66
import * as React from "react";
7+
import { useForm } from "react-hook-form";
78
import { useNavigate } from "react-router-dom";
89
import * as R from "remeda";
910

@@ -20,6 +21,7 @@ const CartItem: React.FC<
2021
disabled?: boolean;
2122
}
2223
> = ({ language, cartProdRel, disabled, removeItemFromCartFunc, ...props }) => {
24+
const { control } = useForm<Record<string, unknown>>();
2325
const cannotModifyOptionsStr =
2426
language === "ko"
2527
? "상품 옵션을 수정하려면 장바구니에서 상품을 삭제한 후 다시 담아주세요."
@@ -48,6 +50,7 @@ const CartItem: React.FC<
4850
optionRel={optionRel}
4951
disabled
5052
disabledReason={cannotModifyOptionsStr}
53+
control={control}
5154
/>
5255
))}
5356
</Stack>

packages/shop/src/components/features/order.tsx

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
import { ErrorBoundary, Suspense } from "@suspensive/react";
1616
import { enqueueSnackbar, OptionsObject } from "notistack";
1717
import * as React from "react";
18+
import { useForm } from "react-hook-form";
1819
import * as R from "remeda";
1920

2021
import ShopHooks from "../../hooks";
@@ -58,7 +59,7 @@ const OrderProductRelationItem: React.FC<OrderProductRelationItemProps> = ({
5859
optionsOfOneItemInOrderPatchMutation,
5960
...props
6061
}) => {
61-
const formRef = React.useRef<HTMLFormElement>(null);
62+
const { control, handleSubmit, getValues } = useForm<Record<string, string>>();
6263
const currentCustomOptionValues: { [k: string]: string } = prodRel.options
6364
.filter((optionRel) => ShopUtils.isOrderProductOptionModifiable(optionRel))
6465
.reduce(
@@ -73,7 +74,7 @@ const OrderProductRelationItem: React.FC<OrderProductRelationItemProps> = ({
7374
enqueueSnackbar(c, { variant, anchorOrigin: { vertical: "bottom", horizontal: "center" } });
7475

7576
const hasPatchableOption = Object.entries(currentCustomOptionValues).length > 0;
76-
const patchOptionBtnDisabled = isPending || !hasPatchableOption;
77+
const patchOptionBtnDisabled = isPending || !hasPatchableOption || order.current_status === "refunded";
7778

7879
const refundOneProductStr = language === "ko" ? "단일 상품 환불" : "Refund one item";
7980
const refundedStr = language === "ko" ? "환불됨" : "Refunded";
@@ -105,9 +106,7 @@ const OrderProductRelationItem: React.FC<OrderProductRelationItemProps> = ({
105106
}
106107
);
107108
const patchOneItemOptions = () => {
108-
if (!Common.Utils.isFormValid(formRef.current)) throw new Error("Form is not valid");
109-
110-
const formValues = Common.Utils.getFormValue<{ [key: string]: string }>({ form: formRef.current });
109+
const formValues = getValues();
111110
const modifiedCustomOptionValues: ShopSchemas.OrderOptionsPatchRequest["options"] = Object.entries(formValues)
112111
.map(([key, value]) => {
113112
// 여기서 key는 order_product_option_relation의 id가 아니라 product_option_group의 id이므로, order_product_option_relation의 id로 변경해야 함
@@ -152,19 +151,14 @@ const OrderProductRelationItem: React.FC<OrderProductRelationItemProps> = ({
152151
summary={<Typography variant="h6">{prodRel.product.name}</Typography>}
153152
actions={actionButtons}
154153
>
155-
<form
156-
ref={formRef}
157-
onSubmit={(e) => {
158-
e.preventDefault();
159-
patchOneItemOptions();
160-
}}
161-
>
154+
<form onSubmit={handleSubmit(() => {})}>
162155
<Stack spacing={2} sx={{ width: "100%" }}>
163156
{prodRel.options.map((optionRel) => (
164157
<CommonComponents.OrderProductRelationOptionInput
165158
key={optionRel.product_option_group.id + (optionRel.product_option?.id || "")}
166159
optionRel={optionRel}
167160
disabled={isPending}
161+
control={control}
168162
/>
169163
))}
170164
</Stack>

0 commit comments

Comments
 (0)