Skip to content
This repository has been archived by the owner on Jul 29, 2024. It is now read-only.

Commit

Permalink
[FE] 카테고리 API 요청 버그 수정 (#184)
Browse files Browse the repository at this point in the history
* fix: 카테고리 CRUD 버그 수정

* feat: 카테고리에서 글목록페이지로 이동시 글목록페이지 리패칭 기능 추가

* chore: 사용하지 않는 상태 삭제

* fix: 렌더링 버그 해결

* refactor: 타입 수정

* refactor: `S.Input` -> `Input` 컴포넌트로 교체

* feat: `useGetQuery` arg에 `enabled` 추가하여 의미없는 API 요청 제거
  • Loading branch information
jeonjeunghoon authored Aug 3, 2023
1 parent a14bbac commit ac153d7
Show file tree
Hide file tree
Showing 10 changed files with 86 additions and 67 deletions.
6 changes: 3 additions & 3 deletions frontend/src/apis/category.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,26 @@ import { AddCategoriesRequest, PatchCategoryArgs } from 'types/apis/category';
// POST: 카테고리 추가
export const addCategory = (body: AddCategoriesRequest) =>
http.post(categoryURL, {
body: JSON.stringify(body),
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});

// GET: 카테고리 목록 조회
export const getCategories = () => http.get(categoryURL);

// GET: 카테고리 글 목록 조회
export const getWritingsInCategory = (categoryId: number | null) =>
export const getWritingsInCategory = (categoryId: number) =>
http.get(`${categoryURL}/${categoryId}`);

// PATCH: 카테고리 수정
export const patchCategory = ({ categoryId, body }: PatchCategoryArgs) =>
http.patch(`${categoryURL}/${categoryId}`, {
body: JSON.stringify(body),
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});

// DELETE: 카테고리 삭제
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,12 @@ export const LongCategoryName: Story = {
<StoryContainer>
{names.map((name) => (
<StoryItemContainer>
<Category id={1} categoryName={name} />
<Category
id={1}
categoryName={name}
getCategories={() => Promise.resolve()}
isDefaultCategory={false}
/>
</StoryItemContainer>
))}
</StoryContainer>
Expand Down
39 changes: 24 additions & 15 deletions frontend/src/components/Category/Category/Category.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { DeleteIcon, PencilIcon } from 'assets/icons';
import { usePageNavigate } from 'hooks/usePageNavigate';
import { KeyboardEvent, MouseEvent, useState } from 'react';
import { KeyboardEventHandler, MouseEventHandler, useState } from 'react';
import { styled } from 'styled-components';
import useCategoryInput from '../useCategoryInput';
import { useCategoryMutation } from '../useCategoryMutation';
import Input from 'components/@common/Input/Input';

type Props = {
id: number;
categoryName: string;
isDefaultCategory: boolean;
getCategories: () => Promise<void>;
};

const Category = ({ id, categoryName }: Props) => {
const Category = ({ id, categoryName, isDefaultCategory, getCategories }: Props) => {
const [name, setName] = useState(categoryName);
const {
value,
Expand All @@ -24,7 +27,7 @@ const Category = ({ id, categoryName }: Props) => {
const { patchCategory, deleteCategory } = useCategoryMutation();
const { goWritingTablePage } = usePageNavigate();

const requestChangedName = async (e: KeyboardEvent<HTMLInputElement>) => {
const requestChangedName: KeyboardEventHandler<HTMLInputElement> = async (e) => {
if (e.key !== 'Enter') return;

setName(value);
Expand All @@ -35,46 +38,52 @@ const Category = ({ id, categoryName }: Props) => {
categoryName: value,
},
});
await getCategories();
};

const openRenamingInput = (e: MouseEvent<SVGSVGElement>) => {
const openRenamingInput: MouseEventHandler<SVGSVGElement> = (e) => {
e.stopPropagation();

setIsInputOpen(true);
};

const deleteCategoryClick = async (e: MouseEvent<SVGSVGElement>) => {
const deleteCategoryClick: MouseEventHandler<SVGSVGElement> = async (e) => {
e.stopPropagation();

await deleteCategory(id);
await getCategories();
};

return (
<S.Container>
{isInputOpen ? (
<S.Input
<Input
type='text'
variant='underlined'
size='small'
placeholder='Change category name ...'
value={value}
ref={inputRef}
onBlur={closeInput}
onChange={handleOnChange}
onKeyDown={escapeRename}
onKeyUp={requestChangedName}
placeholder='Change category name'
/>
) : (
<>
<S.CategoryButton onClick={() => goWritingTablePage(id)}>
<S.Text>{name}</S.Text>
</S.CategoryButton>
<S.IconContainer>
<S.Button>
<PencilIcon onClick={openRenamingInput} width={12} height={12} />
</S.Button>
<S.Button>
<DeleteIcon onClick={deleteCategoryClick} width={12} height={12} />
</S.Button>
</S.IconContainer>
{!isDefaultCategory && (
<S.IconContainer>
<S.Button>
<PencilIcon onClick={openRenamingInput} width={12} height={12} />
</S.Button>
<S.Button>
<DeleteIcon onClick={deleteCategoryClick} width={12} height={12} />
</S.Button>
</S.IconContainer>
)}
</>
)}
</S.Container>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import Accordion from 'components/@common/Accordion/Accordion';
import { styled } from 'styled-components';
import { useCategoryWritings } from 'components/Category/CategorySection/useCategoryWritings';
import Button from 'components/@common/Button/Button';
import { PlusCircleIcon } from 'assets/icons';
import { KeyboardEvent, useState } from 'react';
import { KeyboardEventHandler, useState } from 'react';
import useCategoryInput from '../useCategoryInput';
import Category from '../Category/Category';
import WritingList from '../WritingList/WritingList';
import { useCategoryDetails } from './useCategoryDetails';
import { useCategoryMutation } from '../useCategoryMutation';
import Input from 'components/@common/Input/Input';

const CategorySection = () => {
const [selectedCategoryId, setSelectedCategoryId] = useState<number | null>(null);
const [openItems, setOpenItems] = useState<Record<number, boolean>>({});
const [selectedCategoryId, setSelectedCategoryId] = useState(0);
const { addCategory } = useCategoryMutation();
const writings = useCategoryWritings(selectedCategoryId);
const { categoryDetails, getCategories } = useCategoryDetails(selectedCategoryId, writings);
Expand All @@ -26,41 +25,32 @@ const CategorySection = () => {
closeInput,
} = useCategoryInput('');

const requestAddCategory = async (e: KeyboardEvent<HTMLInputElement>) => {
const requestAddCategory: KeyboardEventHandler<HTMLInputElement> = async (e) => {
if (e.key !== 'Enter') return;

closeInput();
await addCategory({ categoryName: value });
await getCategories();
};

const toggleItem = (selectedCategoryId: number) => {
if (!openItems[selectedCategoryId]) {
setSelectedCategoryId(selectedCategoryId);
}

setOpenItems((prevOpenItems) => ({
...prevOpenItems,
[selectedCategoryId]: !prevOpenItems[selectedCategoryId],
}));
};

if (!categoryDetails) return null;

return (
<S.Section>
<S.Header>
<S.Title>Folders</S.Title>
{isInputOpen ? (
<S.Input
<Input
type='text'
variant='underlined'
size='small'
placeholder='Add category ...'
value={value}
ref={inputRef}
onBlur={closeInput}
onChange={handleOnChange}
onKeyDown={escapeAddCategory}
onKeyUp={requestAddCategory}
placeholder='New category name'
/>
) : (
<S.Button onClick={() => setIsInputOpen(true)}>
Expand All @@ -69,18 +59,19 @@ const CategorySection = () => {
)}
</S.Header>
<Accordion>
{categoryDetails.map((categoryDetail) => {
{categoryDetails.map((categoryDetail, index) => {
return (
<Accordion.Item key={categoryDetail.id}>
<Accordion.Title onIconClick={() => toggleItem(categoryDetail.id)}>
<Category id={categoryDetail.id} categoryName={categoryDetail.categoryName} />
<Accordion.Title onIconClick={() => setSelectedCategoryId(categoryDetail.id)}>
<Category
id={categoryDetail.id}
categoryName={categoryDetail.categoryName}
isDefaultCategory={index === 0}
getCategories={getCategories}
/>
</Accordion.Title>
<Accordion.Panel>
{categoryDetail.writings ? (
<WritingList writings={categoryDetail.writings} />
) : (
<S.NoWritingsText>No Writings inside</S.NoWritingsText>
)}
{categoryDetail.writings && <WritingList writings={categoryDetail.writings} />}
</Accordion.Panel>
</Accordion.Item>
);
Expand Down Expand Up @@ -136,12 +127,4 @@ const S = {
font-weight: 300;
}
`,

NoWritingsText: styled.p`
padding: 0.8rem;
color: ${({ theme }) => theme.color.gray6};
font-size: 1.4rem;
font-weight: 500;
cursor: default;
`,
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@ import { useGetQuery } from '../../../hooks/@common/useGetQuery';
import { getWritingsInCategory } from 'apis/category';
import { useEffect } from 'react';

export const useCategoryWritings = (selectedCategoryId: number | null) => {
export const useCategoryWritings = (selectedCategoryId: number) => {
const { data, getData } = useGetQuery<GetCategoryDetailResponse>({
fetcher: () => getWritingsInCategory(selectedCategoryId),
enabled: Boolean(selectedCategoryId),
});

useEffect(() => {
getData();
const refetch = async () => {
if (!selectedCategoryId) return;

await getData();
};
refetch();
}, [selectedCategoryId]);

return data ? data.writings : null;
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/components/Category/WritingList/WritingList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ type Props = {
const WritingList = ({ writings }: Props) => {
const { goWritingPage } = usePageNavigate();

if (writings.length === 0) return <S.NoWritingsText>No Writings inside</S.NoWritingsText>;

return (
<ul>
{writings.map((writing) => (
Expand Down Expand Up @@ -59,4 +61,12 @@ const S = {
white-space: nowrap;
text-overflow: ellipsis;
`,

NoWritingsText: styled.p`
padding: 0.8rem;
color: ${({ theme }) => theme.color.gray6};
font-size: 1.4rem;
font-weight: 500;
cursor: default;
`,
};
6 changes: 3 additions & 3 deletions frontend/src/components/Category/useCategoryInput.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ChangeEvent, KeyboardEvent, useEffect, useRef, useState } from 'react';
import { ChangeEventHandler, KeyboardEventHandler, useEffect, useRef, useState } from 'react';

const useCategoryInput = (initialValue: string) => {
const [value, setValue] = useState(initialValue);
Expand All @@ -11,7 +11,7 @@ const useCategoryInput = (initialValue: string) => {
}
}, [isInputOpen]);

const handleOnChange = (e: ChangeEvent<HTMLInputElement>) => setValue(e.target.value);
const handleOnChange: ChangeEventHandler<HTMLInputElement> = (e) => setValue(e.target.value);

const resetValue = () => setValue('');

Expand All @@ -20,7 +20,7 @@ const useCategoryInput = (initialValue: string) => {
resetValue();
};

const escapeInput = (e: KeyboardEvent<HTMLInputElement>) => {
const escapeInput: KeyboardEventHandler<HTMLInputElement> = (e) => {
if (e.key !== 'Escape') return;

closeInput();
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/hooks/@common/useGetQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ type UseGetQueryArgs<ResponseData> = {
onSuccess?: (data: ResponseData) => void;
onError?: (error?: Error) => void;
onSettled?: () => void;
enabled?: boolean;
};

export const useGetQuery = <ResponseData>({
fetcher,
onSuccess,
onError,
onSettled,
enabled = true,
}: UseGetQueryArgs<ResponseData>) => {
const [data, setData] = useState<ResponseData | null>(null);
const [isLoading, setIsLoading] = useState(false);
Expand Down Expand Up @@ -47,6 +49,8 @@ export const useGetQuery = <ResponseData>({
}, [fetcher]);

useEffect(() => {
if (!enabled) return;

getData();
}, []);

Expand Down
8 changes: 2 additions & 6 deletions frontend/src/mocks/handlers/category.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const categoryHandlers = [
rest.get(`${categoryURL}/:categoryId`, (req, res, ctx) => {
const categoryId = Number(req.params.categoryId);

if (categoryId !== 1 && categoryId !== 3) return res(ctx.delay(300), ctx.status(404));
if (categoryId !== 1 && categoryId !== 3) return res(ctx.delay(300), ctx.status(500));

if (categoryId === 1) return res(ctx.json(writingsInCategory), ctx.delay(300), ctx.status(200));

Expand All @@ -48,11 +48,7 @@ export const categoryHandlers = [
}),

// 카테고리 삭제
rest.delete(`${categoryURL}/:categoryId`, (req, res, ctx) => {
const categoryId = Number(req.params.categoryId);

if (categoryId !== 200) return res(ctx.delay(300), ctx.status(404));

rest.delete(`${categoryURL}/:categoryId`, (_, res, ctx) => {
return res(ctx.delay(300), ctx.status(204));
}),
];
12 changes: 9 additions & 3 deletions frontend/src/pages/WritingTablePage/WritingTablePage.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
import { getCategoryIdWritingList } from 'apis/writings';
import WritingTable from 'components/WritingTable/WritingTable';
import { useGetQuery } from 'hooks/@common/useGetQuery';
import { PageContext, usePageContext } from 'pages/Layout/Layout';
import { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { styled } from 'styled-components';
import { sidebarStyle } from 'styles/layoutStyle';
import { GetCategoryIdWritingListResponse } from 'types/apis/writings';

const WritingTablePage = () => {
const categoryId = Number(useParams()['categoryId']);

const { data } = useGetQuery<GetCategoryIdWritingListResponse>({
const { data, getData } = useGetQuery<GetCategoryIdWritingListResponse>({
fetcher: () => getCategoryIdWritingList(categoryId),
});

useEffect(() => {
const refetch = async () => {
await getData();
};
refetch();
}, [categoryId]);

return (
<S.Article>
<S.CategoryNameTitle>{data?.categoryName}</S.CategoryNameTitle>
Expand Down

0 comments on commit ac153d7

Please sign in to comment.