From 4e522bfeb86b450db8f10a2ddc989c354f01267d Mon Sep 17 00:00:00 2001 From: wnhlee <2wheeh@gmail.com> Date: Tue, 12 Mar 2024 19:00:38 +0900 Subject: [PATCH] refactor: seperate validateAndGetData and handleApiError to re-use --- .../review/client/create-review-button.tsx | 49 ++----------------- ui/src/context/review/review-context.tsx | 45 +++++++++++++++++ ui/src/hooks/common/use-api-error.ts | 20 ++++++++ .../utils/review/validate-review-fields.ts | 13 +++-- 4 files changed, 78 insertions(+), 49 deletions(-) create mode 100644 ui/src/hooks/common/use-api-error.ts diff --git a/ui/src/components/review/client/create-review-button.tsx b/ui/src/components/review/client/create-review-button.tsx index 50e1418a..f1b3e1e7 100644 --- a/ui/src/components/review/client/create-review-button.tsx +++ b/ui/src/components/review/client/create-review-button.tsx @@ -7,58 +7,15 @@ import Text from '@/components/common/server/text'; import { useToast } from '@/context/common/toast-context'; import { useReview } from '@/context/review/review-context'; -import { useEditorRef } from '@/context/editor/editor-ref-context'; +import { useApiError } from '@/hooks/common/use-api-error'; import { createReview } from '@/lib/apis/review/client'; -import { validateReviewFields } from '@/lib/utils/review/validate-review-fields'; -import { isErrorWithMessage } from '@/lib/utils/common/error'; export function CreateReviewButton() { const router = useRouter(); const { emitToast } = useToast(); - - const { title, movieName, movieNameRef, titleRef, disabled, setDisabled } = useReview(); - const { editorRef } = useEditorRef() ?? {}; - - const validateAndGetData = () => { - if (!editorRef?.current) { - return; - } - - const editorState = editorRef.current.getEditorState(); - - const validatedFields = validateReviewFields({ title, movieName, editorState }); - - if (!validatedFields.success) { - // Order to check should be aligned with the order of elements in the DOM - // title -> movieName -> editor - if (validatedFields.errors.title) { - titleRef.current?.focus(); - emitToast(validatedFields.errors.title, 'error'); - } else if (validatedFields.errors.movieName) { - movieNameRef.current?.focus(); - emitToast(validatedFields.errors.movieName, 'error'); - } else { - editorRef?.current?.focus(); - emitToast(validatedFields.errors.content!, 'error'); - } - - setDisabled(false); - return; - } - - return validatedFields.data; - }; - - const handleApiError = (error: unknown) => { - if (isErrorWithMessage(error)) { - emitToast(error.message, 'error'); - } else { - // TODO: throw new Error and move to global error handler - console.error(error); - emitToast('알 수 없는 에러가 발생했습니다. 다시 시도해주세요.', 'error'); - } - }; + const { disabled, setDisabled, validateAndGetData } = useReview(); + const { handleApiError } = useApiError(); const handleCreateReview = async () => { setDisabled(true); diff --git a/ui/src/context/review/review-context.tsx b/ui/src/context/review/review-context.tsx index 3f6dae32..f98a8da4 100644 --- a/ui/src/context/review/review-context.tsx +++ b/ui/src/context/review/review-context.tsx @@ -1,5 +1,8 @@ 'use client'; +import { useToast } from '@/context/common/toast-context'; +import { useEditorRef } from '@/context/editor/editor-ref-context'; +import { validateReviewFields } from '@/lib/utils/review/validate-review-fields'; import React, { Dispatch, ReactNode, @@ -19,6 +22,13 @@ interface ContextShape { movieNameRef: React.RefObject; disabled: boolean; setDisabled: Dispatch>; + validateAndGetData: () => + | { + title: string; + movieName: string; + content: string; + } + | undefined; } const ReviewContext = createContext(null); @@ -41,6 +51,40 @@ export function ReviewProvider({ const [disabled, setDisabled] = useState(false); + const { editorRef } = useEditorRef() ?? {}; + + const { emitToast } = useToast(); + + const validateAndGetData = () => { + if (!editorRef?.current) { + return; + } + + const editorState = editorRef.current.getEditorState(); + + const validatedFields = validateReviewFields({ title, movieName, editorState }); + + if (!validatedFields.success) { + // Order to check should be aligned with the order of elements in the DOM + // title -> movieName -> editor + if (validatedFields.errors.title) { + titleRef.current?.focus(); + emitToast(validatedFields.errors.title, 'error'); + } else if (validatedFields.errors.movieName) { + movieNameRef.current?.focus(); + emitToast(validatedFields.errors.movieName, 'error'); + } else { + editorRef?.current?.focus(); + emitToast(validatedFields.errors.content!, 'error'); + } + + setDisabled(false); + return; + } + + return validatedFields.data; + }; + return ( {children} diff --git a/ui/src/hooks/common/use-api-error.ts b/ui/src/hooks/common/use-api-error.ts new file mode 100644 index 00000000..27d4bd05 --- /dev/null +++ b/ui/src/hooks/common/use-api-error.ts @@ -0,0 +1,20 @@ +'use client'; + +import { useToast } from '@/context/common/toast-context'; +import { isErrorWithMessage } from '@/lib/utils/common/error'; + +export function useApiError() { + const { emitToast } = useToast(); + + const handleApiError = (error: unknown) => { + if (isErrorWithMessage(error)) { + emitToast(error.message, 'error'); + } else { + // TODO: throw new Error and move to global error handler + console.error(error); + emitToast('알 수 없는 에러가 발생했습니다. 다시 시도해주세요.', 'error'); + } + }; + + return { handleApiError }; +} diff --git a/ui/src/lib/utils/review/validate-review-fields.ts b/ui/src/lib/utils/review/validate-review-fields.ts index 7592404d..a0c2eb3b 100644 --- a/ui/src/lib/utils/review/validate-review-fields.ts +++ b/ui/src/lib/utils/review/validate-review-fields.ts @@ -2,7 +2,6 @@ import type { EditorState, SerializedEditorState } from 'lexical'; import { $isRootTextContentEmptyCurry } from '@lexical/text'; import { getReviewContent } from '@/lib/utils/review/get-review-content'; -import type { CreateReviewRequest } from '@/lib/definitions/review'; interface RawReviewField { title: string; @@ -12,12 +11,20 @@ interface RawReviewField { interface OnSuccess { success: true; - data: CreateReviewRequest; + data: { + title: string; + movieName: string; + content: string; + }; } interface OnFail { success: false; - errors: Partial; + errors: Partial<{ + title: string; + movieName: string; + content: string; + }>; } export function validateReviewFields(rawData: RawReviewField): OnSuccess | OnFail {