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 #1087

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
766a0f8
feat : 리액트 쿼리 설치
gibeom0218 May 12, 2024
7a48590
feat : 로그인 로그아웃 api 변경 (BaseUrl 이름 추후 변경예정)
gibeom0218 May 12, 2024
e9664e9
feat : 이메일 중복 api 수정 및 shared, folder 페이지 접근 권한 설정
gibeom0218 May 12, 2024
02cd63a
feat : 네비게이션바 api 수정 및 코드 수정
gibeom0218 May 12, 2024
d417acc
feat : 폴더 페이지 폴더 버튼 api 수정
gibeom0218 May 12, 2024
8f8c1d6
feat : 전체 폴더링크 가져오기 api 수정 및 코드 수정
gibeom0218 May 12, 2024
2b21a2f
feat : 개별 폴더의 링크 데이터에 대한 api 및 코드 수정
gibeom0218 May 12, 2024
6f8eea8
feat : shard 페이지에서 폴더 id별로 렌더링 api수정 및 코드 수정
gibeom0218 May 12, 2024
aa31f01
refactor : '저장된 링크가 없습니다.' 뜨도록 수정
gibeom0218 May 12, 2024
afc170e
refactor : 주석 수정
gibeom0218 May 12, 2024
f03bf11
refactor : 불필요한 코드 제거 및 주석 수정
gibeom0218 May 13, 2024
812e73c
feat : 공유 모달창에서 shared페이지로 이동 (쿼리 키로 가져오기 위한 임시 기능)
gibeom0218 May 13, 2024
6fcd2b8
feat : 실시간 폴더이름과 소유자 변경
gibeom0218 May 13, 2024
e582d68
feat : 페이지 이동 코드 수정
gibeom0218 May 13, 2024
ed738b2
feat : [추가 구현사항] 링크 추가 기능을 위한 쿼리 저장 및 api 구현 및 코드 수정
gibeom0218 May 13, 2024
35a8485
feat : [추가 구현사항] 실시간 링크 데이터 추가 기능
gibeom0218 May 13, 2024
21c0711
feat : 불필요한 코드 제거
gibeom0218 May 14, 2024
2d52220
feat : 폴더 이름변경 구현
gibeom0218 May 15, 2024
85721b6
feat : 폴더 이름이 폴더 페이지 내부에서도 변경되도록
gibeom0218 May 15, 2024
f0f3991
feat : 폴더 추가 기능 구현
gibeom0218 May 15, 2024
0fbacff
feat : 폴더 삭제 기능 구현
gibeom0218 May 15, 2024
24445a3
feat : 링크 데이터 삭제하기 기능 구현
gibeom0218 May 15, 2024
747091f
feat : 기능 성공시 모달창 닫기
gibeom0218 May 15, 2024
abe6eee
feat : 전체 링크 데이터가 바뀌지 않는 문제 해결
gibeom0218 May 15, 2024
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
6 changes: 5 additions & 1 deletion components/CardList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import star_icon from "@/public/images/star.svg";
import kebab_icon from "@/public/images/kebab.svg";

interface CardListProps {
linkId: number;
url: string;
createdAt: string;
desc: string;
imgUrl: string;
}

export default function CardList({
linkId,
url,
createdAt,
desc,
Expand Down Expand Up @@ -74,7 +76,9 @@ export default function CardList({

return (
<div className={styles.CardList}>
{isDeleteLinkModal && <DeleteLink link={url} onClose={clickDeleteLink} />}
{isDeleteLinkModal && (
<DeleteLink linkId={linkId} link={url} onClose={clickDeleteLink} />
)}
{isAddModal && <Add linkUrl={url} onClose={clickAdd} />}
<img
id={styles.cardImg}
Expand Down
59 changes: 27 additions & 32 deletions components/CardSection.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,21 @@
import React from "react";
import { useEffect, useState } from "react";
import { getFolder } from "@/pages/api/api";
import { useQuery } from "@tanstack/react-query";
import CardList from "@/components/CardList";
import SearchBar from "@/components/SearchBar";
import styles from "@/styles/CardSection.module.css";

interface CardListType {
id: number;
createdAt: string;
created_at: string;
Copy link
Collaborator

Choose a reason for hiding this comment

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

오잉? 혹시 스네이크 케이스로 바꾸신 이유가 있으실까요?

url: string;
description: string;
imageSource: string;
image_source: string;
}

export default function CardSection() {
const [cardList, setCardList] = useState<CardListType[]>([]);

useEffect(() => {
async function getProfileFolder() {
try {
const {
folder: { links },
} = await getFolder();
setCardList(links);
} catch (error) {
console.error(error);
}
}

getProfileFolder();
}, []);
export default function CardSection({ id }: any) {
const cardList = useQuery({
queryKey: ["individualList", Number(id)],
});
Comment on lines +16 to +18
Copy link
Collaborator

Choose a reason for hiding this comment

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

어랏 queryFn이 보이지 않는데 혹시 미완성일까요?

다음과 같이 queryFn이 필요할 듯 합니다:

Suggested change
const cardList = useQuery({
queryKey: ["individualList", Number(id)],
});
const cardList = useQuery({
queryKey: ["individualList", Number(id)],
queryFn: () => getCards(),
});


const dummyFunc = () => {};

Expand All @@ -38,17 +24,26 @@ export default function CardSection() {
<div className={styles.cardFrame}>
<SearchBar onInputChange={dummyFunc} />
<div className={styles.card_list}>
{cardList.map(({ id, createdAt, url, description, imageSource }) => {
return (
<CardList
key={id}
url={url}
createdAt={createdAt}
desc={description}
imgUrl={imageSource}
/>
);
})}
{Array.isArray(cardList.data) &&
cardList.data.map(
({
id,
created_at,
url,
description,
image_source,
}: CardListType) => {
return (
<CardList
Copy link
Collaborator

Choose a reason for hiding this comment

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

cardList의 각 요소들이 CardList를 구성하여 다시 반환하고 있습니다.

이차원 배열이 아닌 이상 CardList의 네이밍이 잘못된게 아닐까 싶어요. Card로 짓는게 어떨까요?

Suggested change
<CardList
<Card

key={id}
url={url}
createdAt={created_at}
desc={description}
imgUrl={image_source}
/>
);
}
)}
</div>
</div>
</div>
Expand Down
51 changes: 27 additions & 24 deletions components/FolderBar.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,38 @@
import React from "react";
import { useEffect, useState } from "react";
import { getFolder } from "@/pages/api/api";
import { useQuery } from "@tanstack/react-query";
import { getFolder, getFolderUser } from "@/pages/api/api";
import styles from "@/styles/FolderBar.module.css";

export default function FolderBar() {
const [folderName, setFolderName] = useState<string>("");
const [userName, setUserName] = useState<string>("");
const [profileImage, setProfileImage] = useState<string>("");
export default function FolderBar({ id }: any) {
//폴더 이름을 가져오기 위한 쿼리
const folderInfo = useQuery({
queryKey: ["folderInfo"],
queryFn: () => getFolder(id),
staleTime: 0,
enabled: id !== undefined,
});

useEffect(() => {
async function getProFileFolder() {
try {
const {
folder: { name, owner },
} = await getFolder();
setFolderName(name);
setUserName(owner.name);
setProfileImage(owner.profileImageSource);
} catch (error) {
console.error(error);
}
}
//폴더 소유자를 가져오기 위한 쿼리
const folderOwner = useQuery({
queryKey: ["folderOwner"],
queryFn: () => getFolderUser(folderInfo.data[0].user_id),
enabled: !!folderInfo.data,
});
Comment on lines +16 to +20
Copy link
Collaborator

Choose a reason for hiding this comment

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

굳굳. 키와 API 함수가 적절하게 들어갔군요.

리액트 쿼리를 쓰게 되면 queries를 커스텀 훅 폴더에 따로 배치해두는 디렉토리 스트럭쳐를 사용하기도 합니다. 쿼리 훅들을 중앙집중화 시키는거지요 !

https://profy.dev/article/react-architecture-api-layer


getProFileFolder();
}, []);
return (
<div className={styles.FolderBar}>
<div className={styles.user}>
<img id={styles.folderImg} src={profileImage} alt="폴더 이미지"></img>
<span id={styles.userName}>@{userName}</span>
<span id={styles.folderName}>{folderName}</span>
<img
id={styles.folderImg}
src={folderOwner.data && folderOwner.data[0]?.image_source}
alt="폴더 이미지"
></img>
<span id={styles.userName}>
@{folderOwner.data && folderOwner.data[0]?.name}
</span>
<span id={styles.folderName}>
{folderInfo.data && folderInfo.data[0]?.name}
</span>
</div>
</div>
);
Expand Down
148 changes: 98 additions & 50 deletions components/FolderSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from "react";
import Image from "next/image";
import { useEffect, useState } from "react";
import { getFolderList, getAllLinks, getFolderLink } from "@/pages/api/api";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import CardList from "@/components/CardList";
import SearchBar from "@/components/SearchBar";
import addImg from "@/public/images/add.svg";
Expand All @@ -28,69 +29,99 @@ interface CardListType {
}

export default function FolderSection() {
const [folderName, setFolderName] = useState("폴더를 선택해주세요");
const [folderList, setFolderList] = useState<FolderListType[]>([]);
const [folderName, setFolderName] = useState<string | undefined>(
"폴더를 선택해주세요"
);
Comment on lines +32 to +34
Copy link
Collaborator

Choose a reason for hiding this comment

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

초기 값이 "폴더를 선택해주세요"로 되어있군요.

초기값을 undefined 혹은 null로 두시고 렌더링 할 때 조건부 렌더링을 통해서 folderName이 없을 경우 "폴더를 선택해주세요."를 출력하는건 어떨까요?

Suggested change
const [folderName, setFolderName] = useState<string | undefined>(
"폴더를 선택해주세요"
);
const [folderName, setFolderName] = useState<string | null>(
null
);

const [folderId, setFolderId] = useState<number | null>(null);
//카드리스트에 관한
const [cardList, setCardList] = useState<CardListType[]>([]);
const [filteredCardList, setFilteredCardList] = useState<CardListType[]>([]);
//모달에 관한
const [isEditNameModal, setIsEditNameModal] = useState<boolean>(false);
const [isAddFolderModal, setIsAddFolderModal] = useState<boolean>(false);
const [isShareModal, setIsShareModal] = useState<boolean>(false);
const [isDeleteFolderModal, setIsDeleteFolderModal] =
useState<boolean>(false);
//선택한 id 버튼 활성화를 위해
const [selectedFolderId, setSelectedFolderId] = useState<number | string>();
//선택한 id 개별 폴더 링크를 가져오기 위해
const [selectedId, setSelectedId] = useState<number>();
const [searchInput, setSearchInput] = useState<string>("");
const [filteredCardList, setFilteredCardList] = useState<CardListType[]>([]);

//전체 폴더 클릭

async function getAllList() {
try {
const { data } = await getAllLinks();
setCardList(data);
setFilteredCardList(data);
setFolderName("전체");
console.log(folderList);
} catch (error) {
console.error(error);
}
}
const queryClient = useQueryClient();

async function folderAllNameClick(all: string) {
setFolderName(all);
await getAllList();
}
//전체 폴더 클릭
//현재 폴더 이름을 최신으로 가져오기 위한
const currentFolderName = useQuery<string>({
queryKey: ["folderName"],
Copy link
Collaborator

Choose a reason for hiding this comment

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

쿼리 키는 한 곳에서 관리할 수도 있습니다 😊

상수를 선언하고 팩토리를 만들어서 동적인 쿼리키를 만들어낼 수 있습니다:

const articleKeys = {
  all: ['articles'] as const,
  list: (page: number) => [...articleKeys.all, 'list', page] as const,
  favoriteList: (page: number) => [...articleKeys.all, 'favorite', page] as const,
  detail: (id: number) => [...articleKeys.all, 'detail', id] as const,
}

const {data} = useQuery(articleKeys.list(page), ()=> getArticle(page));

혹은 다음 코드처럼 쿼리 키를 만들어볼 수도 있습니다:

// queries/queries.ts
import { createQueryKeys, mergeQueryKeys } from "@lukemorales/query-key-factory";

// queries/users.ts
export const users = createQueryKeys('users', {
  all: null,
  detail: (userId: string) => ({
    queryKey: [userId],
    queryFn: () => api.getUser(userId),
  }),
});

예제에서는 lukemorales 라이브러리를 사용하나, 사용하지 않고 return 값만 쿼리키로 잘 구성되면 문제 없을 것 같네요 😊

코드 출처

});

//개별 폴더 클릭
//전체 폴더 가져오기
const allList = useQuery({
queryKey: ["allList"],
queryFn: async () => await getAllLinks(),
});

async function getList(id: number) {
try {
const { data } = await getFolderLink(id);
setCardList(data);
} catch (error) {
console.error(error);
}
//전체 폴더 클릭
async function folderAllNameClick() {
setFolderId(null);
queryClient.setQueryData(["folderId"], null);
queryClient.setQueryData(["folderName"], "전체");
}

async function folderNameClick(name: string, id: number) {
setFolderName(name);
await getList(id);
}
//개별 폴더 가져오기
const individualList = useQuery({
queryKey: ["individualList", selectedId],
queryFn: async () => {
if (selectedId) {
const data = await getFolderLink(selectedId);
return data;
} else {
return []; // 선택된 폴더가 없는 경우 빈 배열 반환
}
Comment on lines +74 to +80
Copy link
Collaborator

Choose a reason for hiding this comment

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

기다린 후 처리되는 로직이 없기에 await할 필요가 없어보입니다 !

Suggested change
queryFn: async () => {
if (selectedId) {
const data = await getFolderLink(selectedId);
return data;
} else {
return []; // 선택된 폴더가 없는 경우 빈 배열 반환
}
queryFn: () => {
if (selectedId) {
return getFolderLink(selectedId);
} else {
return []; // 선택된 폴더가 없는 경우 빈 배열 반환
}

},
});

//개별 폴더 클릭

//폴더 버튼
async function folderNameClick(name: string, id: number) {
setFolderId(id);
queryClient.setQueryData(["folderId"], id);
queryClient.setQueryData(["folderName"], name);
}
//폴더이름을 클릭했을 때 즉각적으로 링크 데이터들이 바뀌도록
useEffect(() => {
async function getList() {
try {
const { data } = await getFolderList();
setFolderList(data);
} catch (error) {
console.error(error);
}
setFolderName(currentFolderName.data);
if (individualList.data && currentFolderName.data !== "전체") {
setCardList(individualList.data);
setFilteredCardList(individualList.data);
} else if (currentFolderName.data === "전체") {
setCardList(allList.data);
setFilteredCardList(allList.data);
}
}, [individualList.data, currentFolderName.data, allList.data]);

//폴더 버튼
const folderList = useQuery({
queryKey: ["folderList"],
queryFn: async () => await getFolderList(),
});

//링크 추가를 위해 현재 폴더 id를 쿼리에 저장
//처음 랜더링 될때 한번
useQuery({
queryKey: ["folderId"],
queryFn: async () => {
return folderId;
},
});
Comment on lines +110 to +115
Copy link
Collaborator

Choose a reason for hiding this comment

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

의도적으로 캐싱을 하신 것으로 보입니다.

필요한 부분에서 folderId를 사용하면 되지 않을까요? useQuery를 사용하지 않아도 되보입니다 😊


getList();
}, []);
//폴더 이름 변경을 위해 현재 폴더 이름을 쿼리에 저장
//처음 랜더링 될때 한번
useQuery({
queryKey: ["folderName"],
queryFn: async () => {
return folderName;
},
});

//이름변경 아이콘 클릭시 뜨는 모달창 함수
const clickEditName = () => {
Expand Down Expand Up @@ -137,32 +168,48 @@ export default function FolderSection() {
<div className={styles.FolderSection_Frame}>
<SearchBar onInputChange={handleInputChange} />
{isEditNameModal && (
<Edit folderName={folderName} onClose={clickEditName} />
<Edit
folderId={folderId}
folderName={folderName}
onClose={clickEditName}
/>
)}
{isAddFolderModal && <AddFolder onClose={clickAddFolder} />}
{isShareModal && <Share folderName={folderName} onClose={clickShare} />}
{isShareModal && (
<Share
folderId={folderId}
folderName={folderName}
onClose={clickShare}
/>
)}
{isDeleteFolderModal && (
<DeleteFolder folderName={folderName} onClose={clickDeleteFolder} />
<DeleteFolder
folderId={folderId}
folderName={folderName}
onClose={clickDeleteFolder}
/>
)}
<div className={styles.FolderBtnList}>
<div className={styles.FolderBtn}>
<button
className={selectedFolderId === "전체" ? styles.active : ""}
onClick={() => {
folderAllNameClick("전체");
folderAllNameClick();
setSelectedFolderId("전체");
setSelectedId(undefined);
}}
>
전체
</button>
{folderList.map(({ name, id }) => {
{folderList.data?.map(({ name, id }: FolderListType) => {
return (
<button
key={id}
className={selectedFolderId === id ? styles.active : ""}
onClick={() => {
folderNameClick(name, id);
setSelectedFolderId(id);
setSelectedId(id);
}}
>
{name}
Expand All @@ -180,7 +227,7 @@ export default function FolderSection() {
/>
</div>
</div>
{cardList[0] ? (
{cardList && cardList[0] ? (
<>
<div className={styles.FolderNameBar}>
<span>{folderName}</span>
Expand All @@ -207,6 +254,7 @@ export default function FolderSection() {
return (
<CardList
key={id}
linkId={id}
url={url}
createdAt={created_at}
desc={description}
Expand Down
Loading
Loading