Skip to content

Commit

Permalink
refactor: seperate validateAndGetData and handleApiError to re-use
Browse files Browse the repository at this point in the history
  • Loading branch information
2wheeh committed Mar 14, 2024
1 parent cf6f36f commit 4e522bf
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 49 deletions.
49 changes: 3 additions & 46 deletions ui/src/components/review/client/create-review-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
45 changes: 45 additions & 0 deletions ui/src/context/review/review-context.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -19,6 +22,13 @@ interface ContextShape {
movieNameRef: React.RefObject<HTMLTextAreaElement>;
disabled: boolean;
setDisabled: Dispatch<SetStateAction<boolean>>;
validateAndGetData: () =>
| {
title: string;
movieName: string;
content: string;
}
| undefined;
}

const ReviewContext = createContext<ContextShape | null>(null);
Expand All @@ -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 (
<ReviewContext.Provider
value={{
Expand All @@ -52,6 +96,7 @@ export function ReviewProvider({
movieNameRef,
disabled,
setDisabled,
validateAndGetData,
}}
>
{children}
Expand Down
20 changes: 20 additions & 0 deletions ui/src/hooks/common/use-api-error.ts
Original file line number Diff line number Diff line change
@@ -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 };
}
13 changes: 10 additions & 3 deletions ui/src/lib/utils/review/validate-review-fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<CreateReviewRequest>;
errors: Partial<{
title: string;
movieName: string;
content: string;
}>;
}

export function validateReviewFields(rawData: RawReviewField): OnSuccess | OnFail {
Expand Down

0 comments on commit 4e522bf

Please sign in to comment.