Skip to content

Commit

Permalink
refactor: 페이지와 컴포넌트에서 서비스로직 분리
Browse files Browse the repository at this point in the history
- 서비스로직을 훅안으로 들고가서 훅에서 주는 함수로 모든 행동 처리하도록 변경
- 로더분리
  • Loading branch information
cksrlcks committed Dec 8, 2024
1 parent 009fd07 commit 2df54ff
Show file tree
Hide file tree
Showing 13 changed files with 122 additions and 89 deletions.
7 changes: 6 additions & 1 deletion src/context/AuthContext.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createContext, useContext, useEffect, useRef, useState } from "react";
import { getUser, login, refreshAccessToken } from "@service/auth";
import { getUser, login, refreshAccessToken, signUp } from "@service/auth";
import { isTokenValid } from "@util/helper";
import { axiosInstance } from "@service/axios";

Expand Down Expand Up @@ -114,6 +114,10 @@ export function AuthProvider({ children }) {
}
}

async function handleSignup(formData) {
return signUp(formData);
}

function clear() {
localStorage.removeItem("accessToken");
localStorage.removeItem("refreshToken");
Expand All @@ -134,6 +138,7 @@ export function AuthProvider({ children }) {
auth,
handleLogin,
handleLogout,
handleSignup,
};

return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
Expand Down
4 changes: 2 additions & 2 deletions src/pages/auth/SignupPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { useEffect } from "react";
import { Navigate, useNavigate } from "react-router-dom";
import { useAuth } from "@context/AuthContext";
import useForm from "@hooks/useForm";
import { signUp } from "@service/auth";
import { Form, FieldItem, Input } from "@components/Field";
import { Button } from "@components/ui";
import AuthContainer from "./components/AuthContainer";
Expand All @@ -11,6 +10,7 @@ import { signupFormSchema } from "./components/schema";
export default function SignupPage() {
const {
auth: { accessToken },
handleSignup,
} = useAuth();
const navigate = useNavigate();

Expand Down Expand Up @@ -38,7 +38,7 @@ export default function SignupPage() {

async function onSubmit(data) {
try {
await signUp(data);
await handleSignup(data);
alert("회원가입에 성공했습니다. \n로그인 페이지로 이동합니다.");
navigate("/login", { replace: true });
} catch (err) {
Expand Down
3 changes: 1 addition & 2 deletions src/pages/items/AllItemsPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { useSearchParams } from "react-router-dom";
import useResponsive from "@hooks/useResponsive";
import useProductList from "./components/useProductList";
import usePagination from "@hooks/usePagination";
import { getProducts } from "@service/product";
import { Select, Button } from "@components/ui";
import { Search, Recent } from "@components/Search";
import { Section } from "@components/Section";
Expand All @@ -28,7 +27,7 @@ export default function AllItemsPage() {
mobile: 3,
});

const { isLoading, error, items, totalCount } = useProductList(getProducts, {
const { isLoading, error, items, totalCount } = useProductList({
page,
pageSize,
keyword,
Expand Down
3 changes: 1 addition & 2 deletions src/pages/items/BestItemsPage.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import useResponsive from "@hooks/useResponsive";
import useProductList from "./components/useProductList";
import { getProducts } from "@service/product";
import { Section } from "@components/Section";
import ProductList from "./components/ProductList";

Expand All @@ -10,7 +9,7 @@ export default function BestItemsPage() {
tablet: 2,
mobile: 1,
});
const { isLoading, error, items } = useProductList(getProducts, {
const { isLoading, error, items } = useProductList({
pageSize,
orderBy: "favorite",
});
Expand Down
20 changes: 3 additions & 17 deletions src/pages/items/ItemAddPage.jsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,13 @@
import { addProduct, uploadProductImage } from "@service/product";
import { PageWrapper } from "@components/Page";
import ProductForm from "./components/ProductForm";
import useProductActions from "./components/useProductActions";

export default function ItemAddPage() {
async function handleSubmit(data) {
try {
if (data.images) {
const imgFormData = new FormData();
imgFormData.append("image", data.images);

const { url } = await uploadProductImage(imgFormData);
data.images = [url];
}

await addProduct(data);
} catch (err) {
throw err;
}
}
const { handleProductAdd } = useProductActions();

return (
<PageWrapper>
<ProductForm onProductSubmit={handleSubmit} />
<ProductForm onProductSubmit={handleProductAdd} />
</PageWrapper>
);
}
26 changes: 0 additions & 26 deletions src/pages/items/ItemDetailPage.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { Suspense } from "react";
import { Await, useLoaderData } from "react-router-dom";
import { getProduct } from "@service/product";
import { getComments } from "@service/comments";
import { PageWrapper } from "@components/Page";
import ProductDetail from "./components/ProductDetail";
import { BackToList } from "@components/Button/BackToList";
Expand Down Expand Up @@ -30,27 +28,3 @@ export default function ItemDetailPage() {
</PageWrapper>
);
}

async function loader({ params }) {
const { id } = params;

// 1. 첫번째 시도 : Promise.all
// 두 요청이 병렬로 시작은 가능하나, 두 요청이 모두 끝나야 반환하므로, 원하는 케이스가 아님

// 2. 두번째 시도 : 하나만 await
// getProduct를 먼저 await하고 getProdcutComments는 promise상태로 담아서 보내기
// getProduct의 await 때문에 코멘트 데이터 요청이 동시에 이뤄지지가 않음 (서비스 호출함수에서 임의로 시간지연으로 테스트해봄)
//const detail = await getProduct(id);
//const comments = getProductComments({ productId: id, limit: 5 });

// 3. 해결한 방법
// 두 요청을 거의 동시에 보내도록 우선 await없이 promise를 각 변수에 할당함
// 여기서 미리 await을 작성하면 그 요청을 기다린후 다음줄의 코드가 실행되어서 동시에 요청이 안됨
const detail = getProduct(id);
const comments = getComments("products", { productId: id, limit: 5 });

// detail에 담긴 promise를 기다린후에 리턴
return { detail: await detail, comments };
}

ItemDetailPage.loader = loader;
24 changes: 24 additions & 0 deletions src/pages/items/ItemDetailPage.loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { getProduct } from "@service/product";
import { getComments } from "@service/comments";

export default async function loader({ params }) {
const { id } = params;

// 1. 첫번째 시도 : Promise.all
// 두 요청이 병렬로 시작은 가능하나, 두 요청이 모두 끝나야 반환하므로, 원하는 케이스가 아님

// 2. 두번째 시도 : 하나만 await
// getProduct를 먼저 await하고 getProdcutComments는 promise상태로 담아서 보내기
// getProduct의 await 때문에 코멘트 데이터 요청이 동시에 이뤄지지가 않음 (서비스 호출함수에서 임의로 시간지연으로 테스트해봄)
//const detail = await getProduct(id);
//const comments = getProductComments({ productId: id, limit: 5 });

// 3. 해결한 방법
// 두 요청을 거의 동시에 보내도록 우선 await없이 promise를 각 변수에 할당함
// 여기서 미리 await을 작성하면 그 요청을 기다린후 다음줄의 코드가 실행되어서 동시에 요청이 안됨
const detail = getProduct(id);
const comments = getComments("products", { productId: id, limit: 5 });

// detail에 담긴 promise를 기다린후에 리턴
return { detail: await detail, comments };
}
36 changes: 4 additions & 32 deletions src/pages/items/ItemModifyPage.jsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import { Navigate, useLoaderData, useNavigate } from "react-router-dom";
import { Navigate, useLoaderData } from "react-router-dom";
import { useAuth } from "@context/AuthContext";
import {
getProduct,
modifyProduct,
uploadProductImage,
} from "@service/product";
import { PageWrapper } from "@components/Page";
import ProductForm from "./components/ProductForm";
import useProductActions from "./components/useProductActions";

export default function ItemModifyPage() {
const { detail } = useLoaderData();
const {
auth: { user },
} = useAuth();
const { handleProductModify } = useProductActions(detail.id);

if (user && user.id !== detail.ownerId) {
alert("권한이 없습니다.");
Expand All @@ -24,38 +21,13 @@ export default function ItemModifyPage() {
images: detail.images[0],
};

async function handleSubmit(data) {
try {
if (data.images !== detail.images[0]) {
const imgFormData = new FormData();
imgFormData.append("image", data.images);

const { url } = await uploadProductImage(imgFormData);
data.images = [url];
}

await modifyProduct(detail.id, data);
} catch (err) {
throw err;
}
}

return (
<PageWrapper>
<ProductForm
initialData={initialData}
onProductSubmit={handleSubmit}
onProductSubmit={(formData) => handleProductModify(detail, formData)}
isEdit
/>
</PageWrapper>
);
}

async function loader({ params }) {
const { id } = params;
const detail = await getProduct(id);

return { detail };
}

ItemModifyPage.loader = loader;
8 changes: 8 additions & 0 deletions src/pages/items/ItemModifyPage.loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { getProduct } from "@service/product";

export default async function loader({ params }) {
const { id } = params;
const detail = await getProduct(id);

return { detail };
}
7 changes: 4 additions & 3 deletions src/pages/items/components/ProductDetail.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Link, useNavigate, useRevalidator } from "react-router-dom";
import { useAuth } from "@context/AuthContext";
import { deleteProduct, toggleLike } from "@service/product";
import {
Chip,
Thumbnail,
Expand All @@ -11,6 +10,7 @@ import {
import { More } from "@components/Button";
import { toWon } from "@util/formatter";
import styles from "./ProductDetail.module.scss";
import useProductActions from "./useProductActions";

export default function ProductDetail({ detail }) {
const {
Expand All @@ -31,13 +31,14 @@ export default function ProductDetail({ detail }) {
} = useAuth();
const navigate = useNavigate();
const { revalidate } = useRevalidator();
const { handleLike, handleProductDelete } = useProductActions(id);
const isOwner = ownerId === user?.id;

async function handleToggleLike() {
if (!user) {
return alert("로그인이 필요합니다.");
}
await toggleLike(id, !isFavorite);
await handleLike(!isFavorite);
revalidate();
}

Expand All @@ -56,7 +57,7 @@ export default function ProductDetail({ detail }) {

if (confirm("정말 삭제할까요?")) {
try {
await deleteProduct(id);
await handleProductDelete(id);
alert("상품을 삭제했습니다.");
navigate("/items");
} catch (err) {
Expand Down
56 changes: 56 additions & 0 deletions src/pages/items/components/useProductActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {
addProduct,
deleteProduct,
modifyProduct,
toggleLike,
uploadProductImage,
} from "@service/product";

export default function useProductActions(productId) {
async function handleLike(flag) {
return toggleLike(productId, flag);
}

async function handleProductAdd(formData) {
try {
if (formData.images) {
const imgFormData = new FormData();
imgFormData.append("image", formData.images);

const { url } = await uploadProductImage(imgFormData);
formData.images = [url];
}

await addProduct(formData);
} catch (err) {
throw err;
}
}

async function handleProductModify(prevData, formData) {
try {
if (formData.images !== prevData.images[0]) {
const imgFormData = new FormData();
imgFormData.append("image", formData.images);

const { url } = await uploadProductImage(imgFormData);
formData.images = [url];
}

await modifyProduct(productId, formData);
} catch (err) {
throw err;
}
}

async function handleProductDelete() {
return deleteProduct(productId);
}

return {
handleLike,
handleProductAdd,
handleProductModify,
handleProductDelete,
};
}
10 changes: 8 additions & 2 deletions src/pages/items/components/useProductList.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { useEffect } from "react";
import { getProducts } from "@service/product";
import useAsync from "@hooks/useAsync";

export default function useProductList(fetchFn, params) {
export default function useProductList(params) {
const { page, pageSize, keyword, orderBy } = params;
const { isLoading, error, result, wrappedFn: getData } = useAsync(fetchFn);
const {
isLoading,
error,
result,
wrappedFn: getData,
} = useAsync(getProducts);
const items = result?.list || [];
const totalCount = result?.totalCount || 0;

Expand Down
7 changes: 5 additions & 2 deletions src/router.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import ItemModifyPage from "./pages/items/ItemModifyPage";
import BoardPage from "./pages/boards/BoardPage";
import { Loading } from "@components/ui/Loading";

import ItemDetailPageLoader from "./pages/items/ItemDetailPage.loader";
import ItemModifyPageLoader from "./pages/items/ItemModifyPage.loader";

export const router = createBrowserRouter(
[
{
Expand Down Expand Up @@ -62,7 +65,7 @@ export const router = createBrowserRouter(
hydrateFallbackElement: (
<Loading>정보를 가져오는 중입니다..</Loading>
),
loader: ItemDetailPage.loader,
loader: ItemDetailPageLoader,
},
],
},
Expand All @@ -84,7 +87,7 @@ export const router = createBrowserRouter(
hydrateFallbackElement: (
<Loading>정보를 가져오는 중입니다..</Loading>
),
loader: ItemModifyPage.loader,
loader: ItemModifyPageLoader,
},
{
path: "boards",
Expand Down

0 comments on commit 2df54ff

Please sign in to comment.