Skip to content

Commit

Permalink
발표 생성 페이지 입력 폼 최적화 및 버그 수정 (#49)
Browse files Browse the repository at this point in the history
* feat: isLoading에 따른 로딩 스피너 추가

* feat: 리액트 쿼리 Provider 구조 변경

* fix: ppt페이지 갯수 증가로 인한 입력창 버벅임 최적화

- 이미지를 가지고 있는 <ControlButtons/>의 리렌더링 최소화
- React.memo로 해당 컴포넌트 래핑 + <ControlButtons/>의 props에 사용되는 함수 useCallback적용

* fix: 이미지 선택 시, 기존 작성 내용 삭제 버그 수정

* fix: 페이지 이동시, 현재 페이지 글자 수가 변하지 않는 버그 수정
  • Loading branch information
minh0518 authored Apr 7, 2024
1 parent 25ed40e commit f9ddb47
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 96 deletions.
67 changes: 51 additions & 16 deletions src/app/(afterlogin)/_components/RQProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<QueryClientProvider client={client}>
{children}
<ReactQueryDevtools initialIsOpen={process.env.NODE_ENV === 'development'} />
<QueryClientProvider client={queryClient}>
{children} <ReactQueryDevtools initialIsOpen={process.env.NODE_ENV === 'development'} />
</QueryClientProvider>
);
}

export default RQProvider;
// function RQProvider({ children }: { children: ReactNode }) {
// const [client] = useState(
// new QueryClient({
// defaultOptions: {
// queries: {
// refetchOnWindowFocus: false,
// retryOnMount: true,
// refetchOnReconnect: false,
// retry: false,
// },
// },
// }),
// );

// return (
// <QueryClientProvider client={client}>
// {children}
// <ReactQueryDevtools initialIsOpen={process.env.NODE_ENV === 'development'} />
// </QueryClientProvider>
// );
// }

// export default RQProvider;
7 changes: 5 additions & 2 deletions src/app/(afterlogin)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: '홈',
Expand All @@ -13,10 +14,12 @@ export const metadata: Metadata = {
export default function AfterLoginLayout({ children }: ReactChildrenProps) {
return (
<div>
<RQProvider>
{/* <RQProvider> */}
<Providers>
<div>{children}</div>
<Toast />
</RQProvider>
</Providers>
{/* </RQProvider> */}
</div>
);
}
12 changes: 7 additions & 5 deletions src/app/(afterlogin)/upload/[id]/_component/ControlButtons.tsx
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -30,7 +33,6 @@ const ControlButtons = ({
presentationData,
setPresentationData,
currentPageIndex,
slug,
changeCurrentPageIndex,
getValues,
errors,
Expand Down Expand Up @@ -209,4 +211,4 @@ const ControlButtons = ({
);
};

export default ControlButtons;
export default React.memo(ControlButtons);
27 changes: 18 additions & 9 deletions src/app/(afterlogin)/upload/[id]/_component/EditPresentation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -30,7 +32,8 @@ const EditPresentation = ({ slug }: EditPresentationProps) => {
const [presentationData, setPresentationData] = useState<UploadDataType>(initialState);
const [currentPageIndex, setCurrpentPageIndex] = useState(0);

const value: UploadDataType = useGetPresentationData(slug);
const { value, isLoading }: { value: UploadDataType; isLoading: boolean } =
useGetPresentationData(slug);

useEffect(() => {
const initailSetting = async () => {
Expand All @@ -48,14 +51,20 @@ const EditPresentation = ({ slug }: EditPresentationProps) => {
}, [value]);

return (
<InputSection
presentationData={presentationData}
setPresentationData={setPresentationData}
currentPageIndex={currentPageIndex}
setCurrpentPageIndex={setCurrpentPageIndex}
slug={slug}
initialState={initialState}
/>
<>
{isLoading ? (
<Spinner />
) : (
<InputSection
presentationData={presentationData}
setPresentationData={setPresentationData}
currentPageIndex={currentPageIndex}
setCurrpentPageIndex={setCurrpentPageIndex}
slug={slug}
initialState={initialState}
/>
)}
</>
);
};

Expand Down
61 changes: 36 additions & 25 deletions src/app/(afterlogin)/upload/[id]/_component/InputSection.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -69,7 +69,7 @@ const InputSection = ({
reset,
setValue,
getValues,
formState: { defaultValues, isSubmitting, isSubmitted, errors },
formState: { isSubmitting, errors },
} = useForm<ValidtaionType>();

useEffect(() => {
Expand All @@ -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 (
<div className={styles.container}>
Expand Down Expand Up @@ -207,6 +217,7 @@ const InputSection = ({
setValue={setValue}
errorForMovePage={errorForMovePage}
/>

<UploadMemo
memo={presentationData.slides[currentPageIndex].memo || ''}
lastDummyPageIndex={presentationData.slides.length - 1}
Expand Down
45 changes: 10 additions & 35 deletions src/app/(afterlogin)/upload/[id]/_component/UploadPpt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,50 +39,25 @@ const UploadPpt = ({
const file = imageRef.current.files[0];

if (file) {
// const reader = new FileReader();
// reader.onloadend = () => {
// 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,
};
});
}
Expand Down
8 changes: 7 additions & 1 deletion src/app/(afterlogin)/upload/[id]/_component/UploadScript.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { ChangeEventHandler, forwardRef, useState } from 'react';
import { ChangeEventHandler, forwardRef, useEffect, useState } from 'react';

import { ValidtaionType } from '@/types/service';

Expand Down Expand Up @@ -56,6 +56,12 @@ const UploadScript = forwardRef<HTMLInputElement, UploadScriptProps>(
setCurrentLength(value.length);
};

useEffect(() => {
(function reset() {
setCurrentLength(script.length);
})();
}, [currentPageIndex, script.length]);

return (
<div className={styles.container}>
<div className={styles.description}>
Expand Down
5 changes: 2 additions & 3 deletions src/app/(afterlogin)/upload/[id]/_hooks/presentation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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();

Expand Down

0 comments on commit f9ddb47

Please sign in to comment.