diff --git a/src/app/(afterlogin)/_components/RQProvider.tsx b/src/app/(afterlogin)/_components/RQProvider.tsx index 9445816..7ab000f 100644 --- a/src/app/(afterlogin)/_components/RQProvider.tsx +++ b/src/app/(afterlogin)/_components/RQProvider.tsx @@ -3,27 +3,62 @@ import React, { ReactNode, useState } from 'react'; import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import { ReactChildrenProps } from '@/types/common'; -function RQProvider({ children }: { children: ReactNode }) { - const [client] = useState( - new QueryClient({ - defaultOptions: { - queries: { - refetchOnWindowFocus: false, - retryOnMount: true, - refetchOnReconnect: false, - retry: false, - }, +function makeQueryClient() { + return new QueryClient({ + defaultOptions: { + queries: { + refetchOnWindowFocus: false, + retryOnMount: true, + refetchOnReconnect: false, + staleTime: 60 * 1000, }, - }), - ); + }, + }); +} + +let browserQueryClient: QueryClient | undefined = undefined; + +function getQueryClient() { + if (typeof window === 'undefined') { + return makeQueryClient(); + } else { + if (!browserQueryClient) browserQueryClient = makeQueryClient(); + return browserQueryClient; + } +} + +export default function Providers({ children }: ReactChildrenProps) { + const queryClient = getQueryClient(); return ( - - {children} - + + {children} ); } -export default RQProvider; +// function RQProvider({ children }: { children: ReactNode }) { +// const [client] = useState( +// new QueryClient({ +// defaultOptions: { +// queries: { +// refetchOnWindowFocus: false, +// retryOnMount: true, +// refetchOnReconnect: false, +// retry: false, +// }, +// }, +// }), +// ); + +// return ( +// +// {children} +// +// +// ); +// } + +// export default RQProvider; diff --git a/src/app/(afterlogin)/layout.tsx b/src/app/(afterlogin)/layout.tsx index e646f26..4e184f1 100644 --- a/src/app/(afterlogin)/layout.tsx +++ b/src/app/(afterlogin)/layout.tsx @@ -4,6 +4,7 @@ import RQProvider from './_components/RQProvider'; import { ReactChildrenProps } from '@/types/common'; import Toast from '../_components/_modules/_modal-pre/Toast'; +import Providers from './_components/RQProvider'; export const metadata: Metadata = { title: '홈', @@ -13,10 +14,12 @@ export const metadata: Metadata = { export default function AfterLoginLayout({ children }: ReactChildrenProps) { return (
- + {/* */} +
{children}
-
+ + {/*
*/}
); } diff --git a/src/app/(afterlogin)/upload/[id]/_component/ControlButtons.tsx b/src/app/(afterlogin)/upload/[id]/_component/ControlButtons.tsx index e47170e..14fd4f2 100644 --- a/src/app/(afterlogin)/upload/[id]/_component/ControlButtons.tsx +++ b/src/app/(afterlogin)/upload/[id]/_component/ControlButtons.tsx @@ -1,17 +1,20 @@ 'use client'; +import React from 'react'; import { Dispatch, MouseEvent, SetStateAction } from 'react'; import Image from 'next/image'; -import { UploadDataType, ValidtaionType } from '@/types/service'; - import styles from './ControlButtons.module.scss'; import classNames from 'classnames/bind'; import { DragDropContext, Draggable, DropResult, Droppable } from 'react-beautiful-dnd'; -import PptImageSvgs from '@/app/(afterlogin)/upload/[id]/_svgs/PptImgSvgs'; + import { FieldErrors, UseFormGetValues } from 'react-hook-form'; + +import { UploadDataType, ValidtaionType } from '@/types/service'; + +import PptImageSvgs from '@/app/(afterlogin)/upload/[id]/_svgs/PptImgSvgs'; import { MAX_LENGTH } from '@/config/const'; const cx = classNames.bind(styles); @@ -30,7 +33,6 @@ const ControlButtons = ({ presentationData, setPresentationData, currentPageIndex, - slug, changeCurrentPageIndex, getValues, errors, @@ -209,4 +211,4 @@ const ControlButtons = ({ ); }; -export default ControlButtons; +export default React.memo(ControlButtons); diff --git a/src/app/(afterlogin)/upload/[id]/_component/EditPresentation.tsx b/src/app/(afterlogin)/upload/[id]/_component/EditPresentation.tsx index 613fa66..e7312ae 100644 --- a/src/app/(afterlogin)/upload/[id]/_component/EditPresentation.tsx +++ b/src/app/(afterlogin)/upload/[id]/_component/EditPresentation.tsx @@ -7,10 +7,12 @@ import { UploadDataType } from '@/types/service'; import { useGetPresentationData } from '../_hooks/presentation'; import InputSection from './InputSection'; +import Spinner from '@/app/_components/_modules/Spinner'; interface EditPresentationProps { slug: number; } + const EditPresentation = ({ slug }: EditPresentationProps) => { const initialState: UploadDataType = { title: null, @@ -30,7 +32,8 @@ const EditPresentation = ({ slug }: EditPresentationProps) => { const [presentationData, setPresentationData] = useState(initialState); const [currentPageIndex, setCurrpentPageIndex] = useState(0); - const value: UploadDataType = useGetPresentationData(slug); + const { value, isLoading }: { value: UploadDataType; isLoading: boolean } = + useGetPresentationData(slug); useEffect(() => { const initailSetting = async () => { @@ -48,14 +51,20 @@ const EditPresentation = ({ slug }: EditPresentationProps) => { }, [value]); return ( - + <> + {isLoading ? ( + + ) : ( + + )} + ); }; diff --git a/src/app/(afterlogin)/upload/[id]/_component/InputSection.tsx b/src/app/(afterlogin)/upload/[id]/_component/InputSection.tsx index 1afa3a8..2584413 100644 --- a/src/app/(afterlogin)/upload/[id]/_component/InputSection.tsx +++ b/src/app/(afterlogin)/upload/[id]/_component/InputSection.tsx @@ -1,6 +1,6 @@ 'use client'; -import { Dispatch, SetStateAction, useEffect, useState } from 'react'; +import { Dispatch, SetStateAction, useEffect, useState, useCallback } from 'react'; import { useForm } from 'react-hook-form'; import { usePatchPresentationData, usePostPresentationData } from '../_hooks/presentation'; import useToggle from '@/app/_hooks/useToggle'; @@ -69,7 +69,7 @@ const InputSection = ({ reset, setValue, getValues, - formState: { defaultValues, isSubmitting, isSubmitted, errors }, + formState: { isSubmitting, errors }, } = useForm(); useEffect(() => { @@ -84,31 +84,41 @@ const InputSection = ({ resetFormData(); }, [presentationData, currentPageIndex]); - const changeCurrentPageIndex = async (nextIndex: number) => { - if (currentPageIndex === presentationData.slides.length - 1) { - setCurrpentPageIndex(nextIndex); - } else { - // 폼 데이터 사용 (watch도 가능) - setErrorForMovePage({ - memo: getValues('memo').length > MAX_LENGTH.MEMO, - script: { - minLength: getValues('script').length === 0, - maxLength: getValues('script').length > MAX_LENGTH.SCRIPT, - }, - }); + const changeCurrentPageIndex = useCallback( + (nextIndex: number) => { + if (currentPageIndex === presentationData.slides.length - 1) { + setCurrpentPageIndex(nextIndex); + } else { + // 폼 데이터 사용 (watch도 가능) + setErrorForMovePage({ + memo: getValues('memo').length > MAX_LENGTH.MEMO, + script: { + minLength: getValues('script').length === 0, + maxLength: getValues('script').length > MAX_LENGTH.SCRIPT, + }, + }); - if ( - errors.script || - errors.memo || - getValues('script').length > MAX_LENGTH.SCRIPT || - getValues('script').length === 0 || - getValues('memo').length > MAX_LENGTH.MEMO - ) - return; + if ( + errors.script || + errors.memo || + getValues('script').length > MAX_LENGTH.SCRIPT || + getValues('script').length === 0 || + getValues('memo').length > MAX_LENGTH.MEMO + ) + return; - setCurrpentPageIndex(nextIndex); - } - }; + setCurrpentPageIndex(nextIndex); + } + }, + [ + currentPageIndex, + errors.memo, + errors.script, + getValues, + presentationData.slides.length, + setCurrpentPageIndex, + ], + ); return (
@@ -207,6 +217,7 @@ const InputSection = ({ setValue={setValue} errorForMovePage={errorForMovePage} /> + { - // setPresentationData((prev) => { - // const shallow = [...prev.slides]; - // shallow[currentPageIndex] = { - // ...shallow[currentPageIndex], - // imageFileId: { - // dataURL: reader.result as string, // 미리보기용 - // file, // 서버용 - // }, - // }; - - // // 추가 - // if (currentPageIndex === prev.slides.length - 1) { - // shallow.push(initialState.slides[0]); - // } - - // return { - // ...prev, - // slides: shallow, - // }; - // }); - // }; - - // reader.readAsDataURL(file); - // const imageResponse = await FileService.fileUpload(file); - // const { id, path } = await imageResponse.json(); const { id, path } = await FileService.fileUpload(file); setPresentationData((prev) => { - const shallow = [...prev.slides]; - shallow[currentPageIndex] = { - ...shallow[currentPageIndex], + const shallow = { ...prev }; + shallow.title = getValues('title'); + const shallowSlides = [...shallow.slides]; + shallowSlides[currentPageIndex] = { + ...shallowSlides[currentPageIndex], + script: getValues('script'), + memo: getValues('memo'), imageFileId: id, imageFilePath: path, }; - // 추가 if (currentPageIndex === prev.slides.length - 1) { - shallow.push(initialState.slides[0]); + shallowSlides.push(initialState.slides[0]); } - return { - ...prev, - slides: shallow, + ...shallow, + slides: shallowSlides, }; }); } diff --git a/src/app/(afterlogin)/upload/[id]/_component/UploadScript.tsx b/src/app/(afterlogin)/upload/[id]/_component/UploadScript.tsx index 3578898..f9e6079 100644 --- a/src/app/(afterlogin)/upload/[id]/_component/UploadScript.tsx +++ b/src/app/(afterlogin)/upload/[id]/_component/UploadScript.tsx @@ -1,6 +1,6 @@ 'use client'; -import { ChangeEventHandler, forwardRef, useState } from 'react'; +import { ChangeEventHandler, forwardRef, useEffect, useState } from 'react'; import { ValidtaionType } from '@/types/service'; @@ -56,6 +56,12 @@ const UploadScript = forwardRef( setCurrentLength(value.length); }; + useEffect(() => { + (function reset() { + setCurrentLength(script.length); + })(); + }, [currentPageIndex, script.length]); + return (
diff --git a/src/app/(afterlogin)/upload/[id]/_hooks/presentation.tsx b/src/app/(afterlogin)/upload/[id]/_hooks/presentation.tsx index a4b1e49..5ccca94 100644 --- a/src/app/(afterlogin)/upload/[id]/_hooks/presentation.tsx +++ b/src/app/(afterlogin)/upload/[id]/_hooks/presentation.tsx @@ -7,7 +7,7 @@ import { useRouter } from 'next/navigation'; // TODO: useSuspenseQuery 사용 버그 처리 export const useGetPresentationData = (slug: number) => { - const { data: value } = useQuery({ + const { data: value, isLoading } = useQuery({ queryKey: ['upload', slug], queryFn: async () => { const res = await clientPptApi.getPresentationData(slug); @@ -16,12 +16,11 @@ export const useGetPresentationData = (slug: number) => { }, }); - return value; + return { value, isLoading }; }; export const usePostPresentationData = (submitAction: 'save' | 'start') => { const router = useRouter(); - const queryClient = useQueryClient(); const { openToast } = useToastStore();