Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[김세동] week20 #1077

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@
"lint": "next lint"
},
"dependencies": {
"@tanstack/react-query": "^5.35.1",
"@tanstack/react-query-devtools": "^5.35.1",
"@types/styled-components": "^5.1.34",
"axios": "^1.6.8",
"cookies-next": "^4.1.1",
"next": "13.5.6",
"react": "^18",
"react-dom": "^18",
Expand Down
253 changes: 249 additions & 4 deletions pages/folder/[id].tsx
Rhajiit marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,4 +1,21 @@
import Folder from ".";
import { useEffect, useRef, useState } from "react";
import useFetch from "@/src/hooks/useFetch";
import router from "next/router";
import FolderUI from "@/src/containers/folder-page/Folder.presenter";

// Function
import { acceptDataFromApi } from "@/src/utils/api";
import refineLinkData from "@/src/utils/refine-link-data/refineLinkData";

// Types
import { UserLinkDataType } from "@/src/types/UserLinkDataType";
import {
LinkCardFunctionDataType,
LinkCardFunctionObjectType,
LinkFolderFunctionObjectType,
} from "@/src/types/ModalFunctionDataTypes";
import FolderListDataType from "@/src/types/FolderListDataType";
type handleCurrentFolderChangeType = (id: number, name: string) => void;
import { Context } from "vm";

export async function getServerSideProps(context: Context) {
Expand All @@ -8,8 +25,236 @@ export async function getServerSideProps(context: Context) {
props: { id },
};
}
/**
* @description 폴더 페이지 컴포넌트
* @returns
*/
export default function Folder({ folderId = 0 }) {
const [isCurrentFolderAll, setIsCurrentFolderAll] = useState(folderId === 0);
const [currentFolderName, setCurrentFolderName] = useState("전체");
const [subFolderList, setSubFolderList] = useState<FolderListDataType[]>([]);
const [isEmptyResponse, setIsEmptyResponse] = useState(true);
const [isLoading, error, acceptDataFromApiAsync] =
useFetch(acceptDataFromApi);
const [currentFolderId, setCurrentFolderId] = useState(0);
const [originItems, setOriginItems] = useState<UserLinkDataType[]>([]);
const [items, setItems] = useState<UserLinkDataType[]>([]);
const [isModalOpened, setIsModalOpened] = useState(false);
const [currentModalType, setCurrentModalType] = useState("removeLink");
const [modalData, setModalData] = useState<LinkCardFunctionDataType>();
const [cardFilterSearchValue, setCardFilterSearchValue] =
useState<string>("");
const [isLinkAddBarHidden, setIsLinkAddBarHidden] = useState<boolean>(false);
const [isFooterVisible, setIsFooterVisible] = useState<boolean>(false);
const [isLinkAddBarVisible, setIsLinkAddBarVisible] =
useState<boolean>(false);

Comment on lines +33 to +50
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Rhajiit

불필요한 상태들이 있고, 한 콤포넌트에서 너무 많은 역할을 가져가고 있습니다
조금 더 구조화를 해보고 콤포넌트를 작성해야 할 것 같아요

const addLinkBarObserveRef = useRef<HTMLDivElement>(null);
const footerObserveRef = useRef<HTMLDivElement>(null);

const handleModalOpen = (
modalType: string,
modalData: LinkCardFunctionDataType,
) => {
setModalData({});
setCurrentModalType(modalType);
if (modalData) {
setModalData(modalData);
}
setIsModalOpened(!isModalOpened);
};

const emptyResponseRecognize = (items: UserLinkDataType[]) => {
if (items.length === 0) {
setIsEmptyResponse(true);
} else {
setIsEmptyResponse(false);
}
};

const handleCurrentFolderChange: handleCurrentFolderChangeType = async (
id,
name,
) => {
setIsEmptyResponse(false);
setCurrentFolderName(name);
setCurrentFolderId(id);
router.push(`/folder/${id}`, undefined, { shallow: true });

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Rhajiit

nextjs useRouter에 shallow가 있던가요?
https://nextjs.org/docs/app/api-reference/functions/use-router#userouter

또한, router push로 인해 렌더될 페이지가 변경되게 되는데
이 이후에 비동기 로직이 실행된다면 실행을 보장하기 어렵지 않을까 생각이 들어요. 어떻게 생각하시나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

folder 페이지를 [id]로 관리하기위해 전환하는 과정에서 불필요한 로직은 뺏다고 생각을 했는데, 아직 남아있었네요..? 확인해보고 수정하겠습니다!

if (id === 0) {
try {
const rawData = await acceptDataFromApiAsync("links");
const data = refineLinkData(rawData);
setIsCurrentFolderAll(true);
setOriginItems(data);
setItems(data);
emptyResponseRecognize(data);
return;
} finally {
}
}
const rawData = await acceptDataFromApiAsync(`folders/${id}/links`);
const data = refineLinkData(rawData);
setOriginItems(data);
setItems(data);
setIsCurrentFolderAll(false);
emptyResponseRecognize(data);
};
Comment on lines +84 to +101
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Rhajiit

folder id가 있는경우와 없는경우가 달라져야 하는 상황인거죠?
이에대한 핸들링은 오늘 이야기 했던것처럼 service레이어가 알아서 관리하도록 하는게 더 좋지 않을까 생각이 들어요.
한번 리팩토링 해볼까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

관련해서 좀 더 고민해보겠습니다!


const handleShareLoad = async () => {
const data = await acceptDataFromApiAsync("folders");
const filterData = data.filter((item: any) => item.id === folderId);
if (filterData.length === 0) {
handleCurrentFolderChange(0, "전체");
return;
}
handleCurrentFolderChange(folderId, filterData[0].name);
};

const acceptSubFolderList = async (requestQuery: string) => {
try {
const data = await acceptDataFromApiAsync(requestQuery);

setSubFolderList(data);
setCurrentFolderId(folderId);
} catch {
setIsEmptyResponse(true);
}
};

useEffect(() => {
if (cardFilterSearchValue === "") {
setItems(originItems);
return;
}
setItems(
originItems.filter(
(item) =>
item.title
.toLowerCase()
.includes(cardFilterSearchValue.toLowerCase()) ||
item.description
.toLowerCase()
.includes(cardFilterSearchValue.toLowerCase()) ||
item.url.toLowerCase().includes(cardFilterSearchValue.toLowerCase()),
),
);
// eslint-disable-next-line react-hooks/exhaustive-deps
Comment on lines +126 to +141
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Rhajiit

필터 값에 따라 데이터를 활용하는 경우, 이렇게 필터된 데이터를 상태로 관리하기보단, 메모화된 상수, 또는 그냥 상수로 관리하는게 더 좋습니다!

const [filter, setFilter] = useState('')
...

const filteredFolders = folders.filter(folder => {
  const titleMatches = item.title.toLowerCase().includes(lowerCaseSearchValue);
  const descriptionMatches = item.description.toLowerCase().includes(lowerCaseSearchValue);
  const urlMatches = item.url.toLowerCase().includes(lowerCaseSearchValue);

  return titleMatches || descriptionMatches || urlMatches;
})


return {
  filteredFolders.map(...
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

알겠습니다! 조언 감사합니다!

}, [cardFilterSearchValue]);

// useEffect를 이용하여 IntersectionObserver을 등록 및 현재 선택 폴더 초기화
useEffect(() => {
// 선택폴더 초기화
acceptSubFolderList(`folders`);
handleShareLoad();

// intersectionObserver 등록
const addLinkBarObserver = new IntersectionObserver((entries) => {
entries.map((entry) => {
if (entry.target === addLinkBarObserveRef.current) {
if (entry.isIntersecting) {
setIsLinkAddBarVisible(true);
} else {
setIsLinkAddBarVisible(false);
}
return 0;
}

if (entry.target === footerObserveRef.current) {
if (entry.isIntersecting) {
setIsFooterVisible(true);
} else {
setIsFooterVisible(false);
}
}
return 0;
});
});
addLinkBarObserver.observe(addLinkBarObserveRef.current!);
addLinkBarObserver.observe(footerObserveRef.current!);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Rhajiit

observer 등록 cleanup이 필요할 것 같아요

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

잘 이해했는지는 모르겠는데 다음과 같이 해보면 어떨까요

useEffect(() => {
  // 선택폴더 초기화
  acceptSubFolderList('folders');
  handleShareLoad();

  // IntersectionObserver 콜백 함수
  const handleIntersection = (entries) => {
    entries.forEach((entry) => {
      if (entry.target === addLinkBarObserveRef.current) {
        setIsLinkAddBarVisible(entry.isIntersecting);
      }

      if (entry.target === footerObserveRef.current) {
        setIsFooterVisible(entry.isIntersecting);
      }
    });
  };

  // intersectionObserver 등록
  const addLinkBarObserver = new IntersectionObserver(handleIntersection);

  const addLinkBarCurrent = addLinkBarObserveRef.current;
  const footerCurrent = footerObserveRef.current;

  if (addLinkBarCurrent) {
    addLinkBarObserver.observe(addLinkBarCurrent);
  }
  if (footerCurrent) {
    addLinkBarObserver.observe(footerCurrent);
  }

  // Cleanup 함수
  return () => {
    if (addLinkBarCurrent) {
      addLinkBarObserver.unobserve(addLinkBarCurrent);
    }
    if (footerCurrent) {
      addLinkBarObserver.unobserve(footerCurrent);
    }
  };
  // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);


useEffect(() => {
if (isFooterVisible || isLinkAddBarVisible) {
setIsLinkAddBarHidden(false);
} else {
setIsLinkAddBarHidden(true);
}
}, [isFooterVisible, isLinkAddBarVisible]);

const handleKebabAction = () => {};

// 카드의 케밥 메뉴의 이름과 각각의 기능이 담긴 객체
const kebabActions: LinkCardFunctionObjectType[] = [
{
Rhajiit marked this conversation as resolved.
Show resolved Hide resolved
buttonName: "삭제하기",
type: "removeLink",
modalHandle: handleModalOpen,
modalButtonAction: handleKebabAction,
},
{
buttonName: "폴더에 추가",
type: "addLinkToFolder",
data: { subfolderList: subFolderList },
modalHandle: handleModalOpen,
modalButtonAction: handleKebabAction,
},
];

// subFolderUtils에 기능, 버튼 이름, 버튼 이미지 등을 전달할 객체
const subFolderAction: LinkFolderFunctionObjectType[] = [
{
Rhajiit marked this conversation as resolved.
Show resolved Hide resolved
buttonName: "공유",
imgUrl: "/assets/icons/svg/share.svg",
imgAlt: "shareButton",
type: "shareFolder",
data: { target: currentFolderName, targetId: currentFolderId },
modalHandle: handleModalOpen,
modalButtonAction: handleKebabAction,
},
{
buttonName: "이름 변경",
imgUrl: "/assets/icons/svg/pen.svg",
imgAlt: "RenameButton",
type: "nameChange",
data: { target: currentFolderName, targetId: currentFolderId },
modalHandle: handleModalOpen,
modalButtonAction: handleKebabAction,
},
{
buttonName: "삭제",
imgUrl: "/assets/icons/svg/trash-can.svg",
imgAlt: "DeleteButton",
type: "removeFolder",
data: { target: currentFolderName, targetId: currentFolderId },
modalHandle: handleModalOpen,
modalButtonAction: handleKebabAction,
},
];

const props = {
isModalOpened,
currentModalType,
modalData,
setIsModalOpened,
handleModalOpen,
subFolderList,
isLinkAddBarHidden,
addLinkBarObserveRef,
handleCurrentFolderChange,
currentFolderName,
isCurrentFolderAll,
cardFilterSearchValue,
setCardFilterSearchValue,
isEmptyResponse,
isLoading,
items,
kebabActions,
footerObserveRef,
subFolderAction,
folderId,
};

export default function FolderId({ id }: { id: string }) {
const folderId = Number(id);
return <Folder folderId={folderId} />;
return <FolderUI props={props} />;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Rhajiit

무적의 FolderUI 콤포넌트가 탄생했군요

콤포넌트 하나에서 모든걸 다 처리하도록 하기보다, 관심사에 맞춰서 기능단, ui단으로 분리해주고
이렇게 분리된 콤포넌트를 조립하는 형태로 개편하는게 좋을 것 같아요

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

기존에 작성된 코드를 뜯어고치는게 쉽지는 않네요... 알겠습니다!

Loading
Loading