diff --git a/src/LecueNote/components/CreateNote/CreateNote.style.ts b/src/LecueNote/components/CreateNote/CreateNote.style.ts
deleted file mode 100644
index 28ce432b..00000000
--- a/src/LecueNote/components/CreateNote/CreateNote.style.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import styled from '@emotion/styled';
-
-export const Wrapper = styled.section`
- display: flex;
- gap: 3.2rem;
- flex-direction: column;
-
- width: 100%;
- margin: 7.8rem 0 3.3rem;
-`;
diff --git a/src/LecueNote/components/CreateNote/index.tsx b/src/LecueNote/components/CreateNote/index.tsx
deleted file mode 100644
index 9aaeb42f..00000000
--- a/src/LecueNote/components/CreateNote/index.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import { CreateNoteProps } from '../../type/lecueNoteType';
-import SelectColor from '../SelectColor';
-import WriteNote from '../WriteNote';
-import * as S from './CreateNote.style';
-
-function CreateNote({
- clickedCategory,
- clickedBgColor,
- clickedTextColor,
- isIconClicked,
- contents,
- setFileName,
- handleChangeFn,
- handleClickCategory,
- handleClickedColorBtn,
- handleClickedIcon,
- imgFile,
- uploadImage,
- binaryImage,
- setPresignedUrl,
- selectedFile
-}: CreateNoteProps) {
- return (
-
-
-
-
- );
-}
-
-export default CreateNote;
diff --git a/src/LecueNote/components/SelectColor/index.tsx b/src/LecueNote/components/SelectColor/index.tsx
index a196c74c..eb2ccbc9 100644
--- a/src/LecueNote/components/SelectColor/index.tsx
+++ b/src/LecueNote/components/SelectColor/index.tsx
@@ -1,3 +1,5 @@
+import React from 'react';
+
import {
BG_COLOR_CHART,
CATEGORY,
@@ -8,19 +10,17 @@ import ShowColorChart from '../ShowColorChart';
import * as S from './SelectColor.style';
function SelectColor({
+ lecueNoteState,
isIconClicked,
- clickedCategory,
- clickedTextColor,
- clickedBgColor,
- setPresignedUrl,
- binaryImage,
- setFileName,
+ presignedUrlDispatch,
+ handleTransformImgFile,
handleCategoryFn,
handleColorFn,
handleIconFn,
- uploadImage,
- selectedFile
+ selectedFile,
}: SelectColorProps) {
+ const { textColor, background, category } = lecueNoteState;
+
return (
@@ -29,7 +29,8 @@ function SelectColor({
{it}
@@ -38,35 +39,18 @@ function SelectColor({
})}
- {clickedCategory === '텍스트색' ? (
-
- ) : (
-
- )}
+
);
}
-export default SelectColor;
+export default React.memo(SelectColor);
diff --git a/src/LecueNote/components/ShowColorChart/index.tsx b/src/LecueNote/components/ShowColorChart/index.tsx
index 507db0e8..452bb58e 100644
--- a/src/LecueNote/components/ShowColorChart/index.tsx
+++ b/src/LecueNote/components/ShowColorChart/index.tsx
@@ -10,17 +10,14 @@ function ShowColorChart({
isIconClicked,
colorChart,
state,
+ handleTransformImgFile,
+ presignedUrlDispatch,
selectedFile,
- setPresignedUrl,
- binaryImage,
- setFileName,
- uploadImage,
handleFn,
handleIconFn,
}: ShowColorChartProps) {
const imgRef = useRef(null);
- // 여기
- useGetPresignedUrl(setPresignedUrl, setFileName);
+ useGetPresignedUrl({ presignedUrlDispatch });
const handleImageUpload = () => {
const fileInput = imgRef.current;
@@ -33,15 +30,18 @@ function ShowColorChart({
reader1.readAsDataURL(file);
reader1.onloadend = () => {
if (reader1.result !== null) {
- uploadImage(reader1.result as string);
+ handleTransformImgFile(reader1.result as string);
}
};
// reader2: 파일을 ArrayBuffer로 읽어서 PUT 요청 수행
const reader2 = new FileReader();
reader2.readAsArrayBuffer(file);
- binaryImage(reader2);
- selectedFile(file);
+ // reader1의 비동기 작업이 완료된 후 수행(onloadend() 활용)
+ reader2.onloadend = () => {
+ handleTransformImgFile(reader2);
+ selectedFile(file);
+ };
}
};
@@ -72,6 +72,7 @@ function ShowColorChart({
- {nickname}
-
+ {nickname}
+
{dateArr[0]}.{dateArr[1]}.{dateArr[2]}
diff --git a/src/LecueNote/hooks/useGetPresignedUrl.ts b/src/LecueNote/hooks/useGetPresignedUrl.ts
index 573b8471..cc859e8d 100644
--- a/src/LecueNote/hooks/useGetPresignedUrl.ts
+++ b/src/LecueNote/hooks/useGetPresignedUrl.ts
@@ -1,26 +1,37 @@
-import { useQuery } from 'react-query';
+import { useEffect, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import getPresignedUrl from '../api/getPresignedUrl';
+import { getPresignedUrlProps } from '../type/lecueNoteType';
-const useGetPresignedUrl = (
- setPresignedUrl: React.Dispatch>,
- setFileName: React.Dispatch>,
-) => {
+const useGetPresignedUrl = ({ presignedUrlDispatch }: getPresignedUrlProps) => {
const navigate = useNavigate();
+ const isUnmounted = useRef(false);
- const { isLoading, data } = useQuery({
- queryKey: ['get-presigned-url'],
- queryFn: () => getPresignedUrl(),
- onError: () => navigate('/error'),
- onSuccess: (data) => {
- setPresignedUrl(data.data.url);
- setFileName(data.data.fileName);
- },
- refetchOnWindowFocus: false,
- });
+ useEffect(() => {
+ isUnmounted.current = false;
+ const fetchData = async () => {
+ try {
+ const { data } = await getPresignedUrl();
- return { isLoading, data };
+ presignedUrlDispatch({
+ type: 'SET_PRESIGNED_URL',
+ presignedUrl: data.url,
+ filename: data.fileName,
+ });
+ } catch (error) {
+ navigate('/error');
+ }
+ };
+
+ if (!isUnmounted.current) {
+ fetchData();
+ }
+
+ return () => {
+ isUnmounted.current = true;
+ };
+ }, []);
};
export default useGetPresignedUrl;
diff --git a/src/LecueNote/page/LeceuNotePage/LecueNotePage.style.ts b/src/LecueNote/page/LeceuNotePage/LecueNotePage.style.ts
index d65164c2..a7bbbf47 100644
--- a/src/LecueNote/page/LeceuNotePage/LecueNotePage.style.ts
+++ b/src/LecueNote/page/LeceuNotePage/LecueNotePage.style.ts
@@ -11,3 +11,12 @@ export const Wrapper = styled.div`
height: 100dvh;
padding: 0 1.7rem;
`;
+
+export const CreateNote = styled.section`
+ display: flex;
+ gap: 3.2rem;
+ flex-direction: column;
+
+ width: 100%;
+ margin: 7.8rem 0 3.3rem;
+`;
diff --git a/src/LecueNote/page/LeceuNotePage/index.tsx b/src/LecueNote/page/LeceuNotePage/index.tsx
index 2d01a324..7e21a0d1 100644
--- a/src/LecueNote/page/LeceuNotePage/index.tsx
+++ b/src/LecueNote/page/LeceuNotePage/index.tsx
@@ -1,11 +1,12 @@
-import { useState } from 'react';
+import { useReducer, useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import Header from '../../../components/common/Header';
import LoadingPage from '../../../components/common/LoadingPage';
import CommonModal from '../../../components/common/Modal/CommonModal';
-import CreateNote from '../../components/CreateNote';
import Footer from '../../components/Footer';
+import SelectColor from '../../components/SelectColor';
+import WriteNote from '../../components/WriteNote';
import {
BG_COLOR_CHART,
CATEGORY,
@@ -13,41 +14,40 @@ import {
} from '../../constants/colorChart';
import usePostLecueNote from '../../hooks/usePostLecueNote';
import usePutPresignedUrl from '../../hooks/usePutPresignedUrl';
+import { reducer } from '../../reducer/lecueNoteReducer';
import * as S from './LecueNotePage.style';
function LecueNotePage() {
const MAX_LENGTH = 1000;
const navigate = useNavigate();
-
- const [contents, setContents] = useState('');
- const [imgFile, setImgFile] = useState('');
- const [imgFile2, setImgFile2] = useState();
- const [clickedCategory, setClickedCategory] = useState(CATEGORY[0]);
- const [clickedTextColor, setClickedTextColor] = useState(TEXT_COLOR_CHART[0]);
- const [clickedBgColor, setClickedBgColor] = useState(BG_COLOR_CHART[0]);
- const [isIconClicked, setIsIconClicked] = useState(false);
- const [fileName, setFileName] = useState(BG_COLOR_CHART[0]);
- const [presignedUrl, setPresignedUrl] = useState('');
- const [file, setFile] = useState();
- const [modalOn, setModalOn] = useState(false);
- const [escapeModal, setEscapeModal] = useState(false);
-
+ const location = useLocation();
const putMutation = usePutPresignedUrl();
const postMutation = usePostLecueNote();
- const location = useLocation();
-
const { bookId } = location.state || {};
- const handleClickCategory = (
- e: React.MouseEvent,
- ) => {
- setClickedCategory(e.currentTarget.innerHTML);
- };
+ const [modalOn, setModalOn] = useState(false);
+ const [escapeModal, setEscapeModal] = useState(false);
+
+ const [lecueNoteState, dispatch] = useReducer(reducer, {
+ presignedUrl: '',
+ filename: BG_COLOR_CHART[0],
+ contents: '',
+ category: CATEGORY[0],
+ textColor: TEXT_COLOR_CHART[0],
+ background: BG_COLOR_CHART[0],
+ file: null,
+ isIconClicked: false,
+ imgToStr: '',
+ imgToBinary: new FileReader(),
+ });
const handleChangeContents = (e: React.ChangeEvent) => {
- setContents(e.target.value);
+ dispatch({ type: 'SET_CONTENTS', contents: e.target.value });
if (e.target.value.length > MAX_LENGTH) {
- setContents((e.target.value = e.target.value.substring(0, MAX_LENGTH)));
+ dispatch({
+ type: 'SET_CONTENTS',
+ contents: (e.target.value = e.target.value.substring(0, MAX_LENGTH)),
+ });
alert('1000자 내로 작성해주세요.');
}
};
@@ -55,34 +55,38 @@ function LecueNotePage() {
const handleClickedColorBtn = (
e: React.MouseEvent,
) => {
- if (clickedCategory === '텍스트색') {
- setClickedTextColor(e.currentTarget.id);
- } else {
- setClickedBgColor(e.currentTarget.id);
- setIsIconClicked(false);
- }
+ e.currentTarget.name === 'textColor'
+ ? dispatch({ type: 'CLICKED_TEXT_COLOR', color: e.currentTarget.id })
+ : dispatch({ type: 'CLICKED_BG_COLOR', color: e.currentTarget.id });
+
+ lecueNoteState.category !== '텍스트색' &&
+ dispatch({ type: 'NOT_CLICKED_IMG_ICON' });
};
- const handleClickedIcon = () => {
- setIsIconClicked(true);
+ const handleTransformImgFile = (file: string | FileReader) => {
+ if (typeof file === 'string') {
+ dispatch({ type: 'IMG_TO_STR', imgFile: file });
+ } else {
+ dispatch({ type: 'IMG_TO_BINARY', imgFile: file });
+ }
};
const handleClickCompleteModal = async () => {
- if (imgFile2) {
- if (imgFile2.result && file) {
+ if (lecueNoteState.imgToBinary) {
+ if (lecueNoteState.imgToBinary.result && lecueNoteState.file) {
putMutation.mutate({
- presignedUrl: presignedUrl,
- binaryFile: imgFile2.result,
- fileType: file.type,
+ presignedUrl: lecueNoteState.presignedUrl,
+ binaryFile: lecueNoteState.imgToBinary.result,
+ fileType: lecueNoteState.file.type,
});
}
}
postMutation.mutate({
- contents: contents,
- color: clickedTextColor,
- fileName: fileName,
- bgColor: clickedBgColor,
- isIconClicked: isIconClicked,
+ contents: lecueNoteState.contents,
+ color: lecueNoteState.textColor,
+ fileName: lecueNoteState.filename,
+ bgColor: lecueNoteState.background,
+ isIconClicked: lecueNoteState.isIconClicked,
bookId: bookId,
});
};
@@ -110,24 +114,35 @@ function LecueNotePage() {
headerTitle="레큐노트 작성"
handleFn={() => setEscapeModal(true)}
/>
- setImgFile(file)}
- setFileName={setFileName}
- handleChangeFn={handleChangeContents}
- handleClickCategory={handleClickCategory}
- handleClickedColorBtn={handleClickedColorBtn}
- handleClickedIcon={handleClickedIcon}
- setPresignedUrl={setPresignedUrl}
- binaryImage={(file) => setImgFile2(file)}
- selectedFile={(file) => setFile(file)}
- />
-
+
+
+
+ handleTransformImgFile(imgFile)}
+ selectedFile={(file: File) =>
+ dispatch({ type: 'SELECTED_FILE', file: file })
+ }
+ handleCategoryFn={(e) =>
+ dispatch({
+ type: 'CLICKED_CATEGORY',
+ category: e.currentTarget.innerHTML,
+ })
+ }
+ handleColorFn={handleClickedColorBtn}
+ handleIconFn={() => dispatch({ type: 'CLICKED_IMG_ICON' })}
+ />
+
+
+
);
}
diff --git a/src/LecueNote/reducer/lecueNoteReducer.ts b/src/LecueNote/reducer/lecueNoteReducer.ts
new file mode 100644
index 00000000..b7616fed
--- /dev/null
+++ b/src/LecueNote/reducer/lecueNoteReducer.ts
@@ -0,0 +1,52 @@
+import { Action, State } from '../type/reducerType';
+
+export const reducer = (state: State, action: Action): State => {
+ switch (action.type) {
+ // presignedUrl을 활용하기 위한 url과 filename을 업데이트하는 동작
+ case 'SET_PRESIGNED_URL':
+ return {
+ ...state,
+ presignedUrl: action.presignedUrl,
+ filename: action.filename,
+ };
+
+ // 노트에 텍스트 작성할 경우 동작
+ case 'SET_CONTENTS':
+ return { ...state, contents: action.contents };
+
+ // 텍스트/ 배경 색 카테고리를 선택한 경우 동작
+ case 'CLICKED_CATEGORY':
+ return { ...state, category: action.category };
+
+ // 텍스트 색을 선택한 경우 동작
+ case 'CLICKED_TEXT_COLOR':
+ return { ...state, textColor: action.color };
+
+ // 배경 색을 선택한 경우 동작
+ case 'CLICKED_BG_COLOR':
+ return { ...state, background: action.color };
+
+ // 이미지 파일을 선택한 경우 동작
+ case 'SELECTED_FILE':
+ return { ...state, file: action.file };
+
+ // 이미지 선택 아이콘을 클릭한 경우 동작
+ case 'CLICKED_IMG_ICON':
+ return { ...state, isIconClicked: true };
+
+ // 이미지 선택 아이콘을 클릭하지 않은 경우 동작
+ case 'NOT_CLICKED_IMG_ICON':
+ return { ...state, isIconClicked: false };
+
+ // 이미지 파일을 string 주소 값으로 반환
+ case 'IMG_TO_STR':
+ return { ...state, imgToStr: action.imgFile };
+
+ // 이미지 파일을 binary 값으로 반환
+ case 'IMG_TO_BINARY':
+ return { ...state, imgToBinary: action.imgFile };
+
+ default:
+ throw new Error('Unhandled action');
+ }
+};
diff --git a/src/LecueNote/type/lecueNoteType.ts b/src/LecueNote/type/lecueNoteType.ts
index 604abd73..787f67d1 100644
--- a/src/LecueNote/type/lecueNoteType.ts
+++ b/src/LecueNote/type/lecueNoteType.ts
@@ -1,60 +1,47 @@
-export interface CreateNoteProps {
- clickedCategory: string;
- clickedTextColor: string;
- clickedBgColor: string;
- isIconClicked: boolean;
- imgFile: string;
- contents: string;
- selectedFile: (file: File) => void;
- setPresignedUrl: React.Dispatch>;
- setFileName: React.Dispatch>;
- handleChangeFn: (e: React.ChangeEvent) => void;
- handleClickCategory: (
- e: React.MouseEvent,
- ) => void;
- handleClickedColorBtn: (
- e: React.MouseEvent,
- ) => void;
- handleClickedIcon: () => void;
- uploadImage: (file: string) => void;
- binaryImage: (file: FileReader) => void;
-}
-
export interface SelectColorProps {
isIconClicked: boolean;
- clickedCategory: string;
- clickedTextColor: string;
- clickedBgColor: string;
+ lecueNoteState: {
+ textColor: string;
+ background: string;
+ category?: string;
+ };
selectedFile: (file: File) => void;
- setPresignedUrl: React.Dispatch>;
- setFileName: React.Dispatch>;
+ presignedUrlDispatch: React.Dispatch<{
+ type: 'SET_PRESIGNED_URL';
+ presignedUrl: string;
+ filename: string;
+ }>;
handleCategoryFn: (
e: React.MouseEvent,
) => void;
handleColorFn: (e: React.MouseEvent) => void;
handleIconFn: () => void;
- uploadImage: (file: string) => void;
- binaryImage: (file: FileReader) => void;
+ handleTransformImgFile: (file: string | FileReader) => void;
}
export interface ShowColorChartProps {
isIconClicked: boolean;
colorChart: string[];
state: string;
+ handleTransformImgFile: (file: string | FileReader) => void;
selectedFile: (file: File) => void;
- setPresignedUrl: React.Dispatch>;
- setFileName: React.Dispatch>;
- uploadImage: (file: string) => void;
handleFn: (e: React.MouseEvent) => void;
handleIconFn: () => void;
- binaryImage: (file: FileReader) => void;
+ presignedUrlDispatch: React.Dispatch<{
+ type: 'SET_PRESIGNED_URL';
+ presignedUrl: string;
+ filename: string;
+ }>;
}
export interface WriteNoteProps {
imgFile: string;
isIconClicked: boolean;
- clickedBgColor: string;
- clickedTextColor: string;
+ lecueNoteState: {
+ textColor: string;
+ background: string;
+ category?: string;
+ };
contents: string;
handleChangeFn: (e: React.ChangeEvent) => void;
}
@@ -64,9 +51,12 @@ export interface FooterProps {
setModalOn: React.Dispatch>;
}
-export interface getPresignedUrlPrps {
- setPresignedUrl: React.Dispatch>;
- setFileName: React.Dispatch>;
+export interface getPresignedUrlProps {
+ presignedUrlDispatch: React.Dispatch<{
+ type: 'SET_PRESIGNED_URL';
+ presignedUrl: string;
+ filename: string;
+ }>;
}
export interface putPresignedUrlProps {
diff --git a/src/LecueNote/type/reducerType.ts b/src/LecueNote/type/reducerType.ts
new file mode 100644
index 00000000..bd8a244d
--- /dev/null
+++ b/src/LecueNote/type/reducerType.ts
@@ -0,0 +1,24 @@
+export type State = {
+ presignedUrl: string;
+ filename: string;
+ contents: string;
+ category: string;
+ textColor: string;
+ background: string;
+ file: File | null;
+ isIconClicked: boolean;
+ imgToStr: string;
+ imgToBinary: FileReader;
+};
+
+export type Action =
+ | { type: 'SET_PRESIGNED_URL'; presignedUrl: string; filename: string }
+ | { type: 'SET_CONTENTS'; contents: string }
+ | { type: 'CLICKED_CATEGORY'; category: string }
+ | { type: 'CLICKED_TEXT_COLOR'; color: string }
+ | { type: 'CLICKED_BG_COLOR'; color: string }
+ | { type: 'SELECTED_FILE'; file: File }
+ | { type: 'CLICKED_IMG_ICON' }
+ | { type: 'NOT_CLICKED_IMG_ICON' }
+ | { type: 'IMG_TO_STR'; imgFile: string }
+ | { type: 'IMG_TO_BINARY'; imgFile: FileReader };