From 55bc7d8fb3afcdc440b5388608c2374faccc6ed3 Mon Sep 17 00:00:00 2001
From: stakbucks <dhassidu11@gmail.com>
Date: Sat, 6 Apr 2024 18:47:21 +0900
Subject: [PATCH] refactor: create-character

---
 .../hooks/useCreateCharacter.tsx              |  4 +-
 .../create-character/steps/select-color.tsx   |  7 ++--
 .../create-character/steps/select-item.tsx    |  4 +-
 .../steps/select-keywords.tsx                 |  5 +--
 .../create-character/steps/select-shape.tsx   |  4 +-
 .../create-character/steps/set-name.tsx       |  4 +-
 .../create-character/steps/show-result.tsx    | 21 +++++------
 src/context/create-character-provider.tsx     | 37 +++++++++++++------
 8 files changed, 49 insertions(+), 37 deletions(-)

diff --git a/src/components/create-character/hooks/useCreateCharacter.tsx b/src/components/create-character/hooks/useCreateCharacter.tsx
index 1d02d84..65f056b 100644
--- a/src/components/create-character/hooks/useCreateCharacter.tsx
+++ b/src/components/create-character/hooks/useCreateCharacter.tsx
@@ -1,10 +1,10 @@
 'use client';
 
 import { useContext } from 'react';
-import { CreateCharacterDispatchContext } from '@/context/create-character-provider';
+import { CreateCharacterContext } from '@/context/create-character-provider';
 
 export default function useCreateCharacter() {
-    const createCharacterDispatch = useContext(CreateCharacterDispatchContext);
+    const createCharacterDispatch = useContext(CreateCharacterContext);
 
     if (createCharacterDispatch === null) throw new Error('CreateCharacter Provider를 감싸서 사용하세요');
 
diff --git a/src/components/create-character/steps/select-color.tsx b/src/components/create-character/steps/select-color.tsx
index e6c7c32..c4728d9 100644
--- a/src/components/create-character/steps/select-color.tsx
+++ b/src/components/create-character/steps/select-color.tsx
@@ -1,8 +1,7 @@
 import React, { useContext, useState } from 'react';
 import { CarouselDispatchContext } from '..';
-import Header from '../header';
 import Intro from '../intro';
-import { CreateCharacterDispatchContext } from '@/context/create-character-provider';
+import { useCreateCharacterContext } from '@/context/create-character-provider';
 import CtaButton from '@/components/ui/cta-button';
 import CheckCircle from '@/assets/icons/check-circle.svg';
 import FixedBottomArea from '../fixed-bottom-area';
@@ -12,13 +11,13 @@ type ColorId = (typeof Colors)[number]['id'];
 
 export default React.memo(function SelectColor() {
     const [selected, setSelected] = useState<null | ColorId>(null);
-    const createCharacterDispatch = useContext(CreateCharacterDispatchContext);
+    const { setValue } = useCreateCharacterContext();
     const carouselDispatch = useContext(CarouselDispatchContext);
 
     const handleClick = () => {
         if (selected === null) return;
 
-        createCharacterDispatch?.setValue('colorIdx', selected);
+        setValue('colorIdx', selected);
         carouselDispatch?.handleNextClick();
     };
 
diff --git a/src/components/create-character/steps/select-item.tsx b/src/components/create-character/steps/select-item.tsx
index a9207ed..199aec7 100644
--- a/src/components/create-character/steps/select-item.tsx
+++ b/src/components/create-character/steps/select-item.tsx
@@ -2,16 +2,16 @@ import Header from '../header';
 import Intro from '../intro';
 import React, { useState } from 'react';
 import useCarousel from '../hooks/useCarousel';
-import useCreateCharacter from '../hooks/useCreateCharacter';
 import FixedBottomArea from '../fixed-bottom-area';
 import { Items, NO_ITEM_IDX } from '@/constants/character-info';
 import CtaButton from '@/components/ui/cta-button';
+import { useCreateCharacterContext } from '@/context/create-character-provider';
 
 type ItemId = (typeof Items)[number]['id'];
 
 export default React.memo(function SelectItem() {
     const [selected, setSelected] = useState<null | ItemId>(null);
-    const { setValue } = useCreateCharacter();
+    const { setValue } = useCreateCharacterContext();
     const { handleNextClick } = useCarousel();
 
     const handleClick = () => {
diff --git a/src/components/create-character/steps/select-keywords.tsx b/src/components/create-character/steps/select-keywords.tsx
index f5bf910..3f732a8 100644
--- a/src/components/create-character/steps/select-keywords.tsx
+++ b/src/components/create-character/steps/select-keywords.tsx
@@ -4,15 +4,15 @@ import React, { useState } from 'react';
 import CtaButton from '@/components/ui/cta-button';
 import useCarousel from '../hooks/useCarousel';
 import FixedBottomArea from '../fixed-bottom-area';
-import useCreateCharacter from '../hooks/useCreateCharacter';
 import { Keywords } from '@/constants/character-info';
+import { useCreateCharacterContext } from '@/context/create-character-provider';
 
 type KeywordId = (typeof Keywords)[number]['id'];
 
 export default React.memo(function SelectKeywords() {
     const { handleNextClick } = useCarousel();
 
-    const { setValue } = useCreateCharacter();
+    const { setValue } = useCreateCharacterContext();
 
     const [selected, setSelected] = useState<Array<KeywordId>>([]);
 
@@ -28,7 +28,6 @@ export default React.memo(function SelectKeywords() {
 
     const handleNextButtonClick = () => {
         if (selected.length === 0) {
-            // TODO: 나중에 toast로 바꾸기
             alert('키워드를 한 개 이상 선택해 주세요.');
             return;
         }
diff --git a/src/components/create-character/steps/select-shape.tsx b/src/components/create-character/steps/select-shape.tsx
index a5c68bc..2c0d76f 100644
--- a/src/components/create-character/steps/select-shape.tsx
+++ b/src/components/create-character/steps/select-shape.tsx
@@ -6,15 +6,15 @@ import React, { useState } from 'react';
 import FixedBottomArea from '../fixed-bottom-area';
 import Header from '../header';
 import useCarousel from '../hooks/useCarousel';
-import useCreateCharacter from '../hooks/useCreateCharacter';
 import Intro from '../intro';
+import { useCreateCharacterContext } from '@/context/create-character-provider';
 
 type ShapeId = (typeof Shapes)[number]['id'];
 
 export default React.memo(function SelectShape() {
     const [selected, setSelected] = useState<null | ShapeId>(null);
 
-    const { setValue } = useCreateCharacter();
+    const { setValue } = useCreateCharacterContext();
     const { handleNextClick } = useCarousel();
 
     const handleSelect = (id: ShapeId) => {
diff --git a/src/components/create-character/steps/set-name.tsx b/src/components/create-character/steps/set-name.tsx
index 15085b3..ef6d89c 100644
--- a/src/components/create-character/steps/set-name.tsx
+++ b/src/components/create-character/steps/set-name.tsx
@@ -4,11 +4,13 @@ import Intro from '../intro';
 import useCarousel from '../hooks/useCarousel';
 import useCreateCharacter from '../hooks/useCreateCharacter';
 import FixedBottomArea from '../fixed-bottom-area';
+import { useCreateCharacterContext } from '@/context/create-character-provider';
 
 const NAME_REGEX = /^[ㄱ-ㅎ|ㅏ-ㅣ|가-힣a-zA-Z\s]{1,8}$/;
 
 export default React.memo(function SetName() {
-    const { setValue } = useCreateCharacter();
+    console.log('name rerender');
+    const { setValue } = useCreateCharacterContext();
     const { handleNextClick } = useCarousel();
     const nameRef = useRef<HTMLInputElement | null>(null);
 
diff --git a/src/components/create-character/steps/show-result.tsx b/src/components/create-character/steps/show-result.tsx
index c99f594..8533535 100644
--- a/src/components/create-character/steps/show-result.tsx
+++ b/src/components/create-character/steps/show-result.tsx
@@ -3,7 +3,11 @@
 import HomeBg from '@/assets/images/homeBg.jpg';
 import CtaButton from '@/components/ui/cta-button';
 import { Keywords, NO_ITEM_IDX } from '@/constants/character-info';
-import { CreateCharacterValues, CreateCharacterValuesContext } from '@/context/create-character-provider';
+import {
+    CreateCharacterContext,
+    CreateCharacterValues,
+    useCreateCharacterContext,
+} from '@/context/create-character-provider';
 import { sendGTMEvent } from '@next/third-parties/google';
 import { getCookie, setCookie } from 'cookies-next';
 import Image from 'next/image';
@@ -35,15 +39,10 @@ export default function ShowResult() {
     const searchParams = useSearchParams();
     const step = searchParams.get('step');
 
-    const createCharacterValues = useContext(CreateCharacterValuesContext);
+    const { getValues } = useCreateCharacterContext();
+    const values = getValues();
 
-    if (createCharacterValues === null) {
-        alert('처음부터 하세요.');
-        router.push('/');
-        throw new Error('');
-    }
-
-    const { colorIdx, shapeIdx, name, keywords, itemIdx } = createCharacterValues;
+    const { colorIdx, shapeIdx, name, keywords, itemIdx } = values;
     const characterImg = `https://kr.object.ncloudstorage.com/kekeche-character/character/${shapeIdx}/0/${colorIdx}.webp`;
     const itemImg =
         itemIdx !== NO_ITEM_IDX ? `https://kr.object.ncloudstorage.com/kekeche-character/item/${itemIdx}.webp` : null;
@@ -54,14 +53,14 @@ export default function ShowResult() {
     const handleNextBtnClick = async () => {
         try {
             // 캐릭터 생성 api...
-            const { id } = await createCharacter(createCharacterValues, `${getCookie('accessToken')}`);
+            const { id } = await createCharacter(values, `${getCookie('accessToken')}`);
             router.push(`/character/${id}`);
             router.refresh();
             sendGTMEvent({ event: 'createCharacter' });
         } catch (err) {
             // 로그인 안 한 사람->'앗' 페이지
             console.log(err);
-            setCookie('create-character', JSON.stringify(createCharacterValues));
+            setCookie('create-character', JSON.stringify(values));
             handleNextClick();
         }
     };
diff --git a/src/context/create-character-provider.tsx b/src/context/create-character-provider.tsx
index 8494fb0..d3f2343 100644
--- a/src/context/create-character-provider.tsx
+++ b/src/context/create-character-provider.tsx
@@ -1,6 +1,6 @@
 'use client';
 
-import { createContext, useCallback, useMemo, useRef, useState } from 'react';
+import { createContext, useCallback, useContext, useMemo, useRef } from 'react';
 
 type Props = {
     children: React.ReactNode;
@@ -15,12 +15,13 @@ export interface CreateCharacterValues {
     itemIdx?: number | null;
 }
 
-interface CreateCharacterDispatch {
+interface CreateCharacterContext {
     setValue<T extends keyof CreateCharacterValues>(target: T, value: CreateCharacterValues[T]): void;
+    getValue<T extends keyof CreateCharacterValues>(target: T): CreateCharacterValues[T];
+    getValues(): CreateCharacterValues;
 }
 
-export const CreateCharacterValuesContext = createContext<null | CreateCharacterValues>(null);
-export const CreateCharacterDispatchContext = createContext<null | CreateCharacterDispatch>(null);
+export const CreateCharacterContext = createContext<null | CreateCharacterContext>(null);
 
 export default function CreateCharacterProvider({ children }: Props) {
     const createCharacterValues = useRef<CreateCharacterValues>({});
@@ -35,13 +36,25 @@ export default function CreateCharacterProvider({ children }: Props) {
         [],
     );
 
-    const memoizedSetValue = useMemo(() => ({ setValue }), [setValue]);
+    const getValue = useCallback(<T extends keyof CreateCharacterValues>(target: T) => {
+        return createCharacterValues.current[target];
+    }, []);
 
-    return (
-        <CreateCharacterValuesContext.Provider value={createCharacterValues.current}>
-            <CreateCharacterDispatchContext.Provider value={memoizedSetValue}>
-                {children}
-            </CreateCharacterDispatchContext.Provider>
-        </CreateCharacterValuesContext.Provider>
-    );
+    const getValues = useCallback(() => {
+        return createCharacterValues.current;
+    }, []);
+
+    const memoizedValue = useMemo(() => ({ setValue, getValue, getValues }), [getValue, getValues, setValue]);
+
+    return <CreateCharacterContext.Provider value={memoizedValue}>{children}</CreateCharacterContext.Provider>;
+}
+
+export function useCreateCharacterContext() {
+    const createCharacterContext = useContext(CreateCharacterContext);
+
+    if (createCharacterContext === null) {
+        throw new Error('CreateCharacterContext를 감싸서 사용하세요.');
+    }
+
+    return createCharacterContext;
 }