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

[FE] Refactor/#645: Home 페이지 API 로직 수정 및 React-Query 적용 #646

Merged
merged 6 commits into from
Feb 5, 2024
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
35 changes: 35 additions & 0 deletions frontend/src/api/http.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';

const API_POSTFIX = 'api';
const BASE_URL = process.env.APP_URL || `https://mapbefine.com/${API_POSTFIX}`;

const token = localStorage.getItem('userToken');

const axiosInstance = axios.create({
baseURL: BASE_URL,
headers: token ? { Authorization: `Bearer ${token}` } : {},
Copy link
Collaborator

Choose a reason for hiding this comment

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

token 없으면 Authorization: undefined로 들어가서 분기처리 해줘야하는게 맞겠져?

Copy link
Collaborator

Choose a reason for hiding this comment

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

오호.. 이 부분을 까먹었군요! 저도 변경 해야겠습니다! 굿 👍

});

export interface HttpClient extends AxiosInstance {
get<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<T>;
post<T = unknown>(
url: string,
data?: any,
config?: AxiosRequestConfig,
): Promise<T>;
patch<T = unknown>(
url: string,
data?: any,
config?: AxiosRequestConfig,
): Promise<T>;
put<T = unknown>(
url: string,
data?: any,
config?: AxiosRequestConfig,
): Promise<T>;
delete<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<T>;
}

export const http: HttpClient = axiosInstance;

http.interceptors.response.use((res) => res.data);
4 changes: 4 additions & 0 deletions frontend/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { TopicCardProps } from '../types/Topic';
import { http } from './http';

export const getTopics = (url: string) => http.get<TopicCardProps[]>(url);
8 changes: 5 additions & 3 deletions frontend/src/components/TopicCard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { SyntheticEvent, useContext, useState } from 'react';
import { QueryObserverResult, RefetchOptions } from '@tanstack/react-query';
import { useContext, useState } from 'react';
import { styled } from 'styled-components';

import SeeTogetherSVG from '../../assets/seeTogetherBtn_filled.svg';
Expand Down Expand Up @@ -26,7 +27,9 @@ interface OnClickDesignatedProps {
interface TopicCardExtendedProps extends TopicCardProps {
cardType: 'default' | 'modal';
onClickDesignated?: ({ topicId, topicName }: OnClickDesignatedProps) => void;
getTopicsFromServer?: () => void;
getTopicsFromServer?: (
options?: RefetchOptions | undefined,
) => Promise<QueryObserverResult<TopicCardProps[], Error>>;
}
Comment on lines +30 to +32
Copy link
Collaborator

Choose a reason for hiding this comment

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

QueryObserverResult에 대한 설명을 들을 수 있을까욧?!! 찾아봤는데 이해가 안 가서요.. 흑흑


function TopicCard({
Expand All @@ -39,7 +42,6 @@ function TopicCard({
pinCount,
bookmarkCount,
isInAtlas,
isBookmarked,
onClickDesignated,
getTopicsFromServer,
}: TopicCardExtendedProps) {
Expand Down
23 changes: 3 additions & 20 deletions frontend/src/components/TopicCardContainer/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { Swiper, Tab } from 'map-befine-swiper';
import { useEffect, useState } from 'react';
import { styled } from 'styled-components';

import useGet from '../../apiHooks/useGet';
import useTopicsQuery from '../../hooks/api/useTopicsQuery';
import useKeyDown from '../../hooks/useKeyDown';
import { TopicCardProps } from '../../types/Topic';
import Box from '../common/Box';
import Flex from '../common/Flex';
import Space from '../common/Space';
Expand All @@ -25,23 +23,8 @@ function TopicCardContainer({
containerDescription,
routeWhenSeeAll,
}: TopicCardContainerProps) {
const [topics, setTopics] = useState<TopicCardProps[] | null>(null);
const { topics, refetchTopics } = useTopicsQuery(url);
const { elementRef, onElementKeyDown } = useKeyDown<HTMLSpanElement>();
const { fetchGet } = useGet();

const setTopicsFromServer = async () => {
await fetchGet<TopicCardProps[]>(
url,
'지도를 가져오는데 실패했습니다. 잠시 후 다시 시도해주세요.',
(response) => {
setTopics(response);
},
);
};

useEffect(() => {
setTopicsFromServer();
}, []);

return (
<section>
Expand Down Expand Up @@ -112,7 +95,7 @@ function TopicCardContainer({
bookmarkCount={topic.bookmarkCount}
isInAtlas={topic.isInAtlas}
isBookmarked={topic.isBookmarked}
getTopicsFromServer={setTopicsFromServer}
getTopicsFromServer={refetchTopics}
/>
<CustomSpace />
</Flex>
Expand Down
13 changes: 13 additions & 0 deletions frontend/src/hooks/api/useTopicsQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useQuery } from '@tanstack/react-query';

import { getTopics } from '../../api';

const useTopicsQuery = (url: string) => {
const { data: topics, refetch: refetchTopics } = useQuery({
Copy link
Collaborator

Choose a reason for hiding this comment

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

refetch 메서드를 통해 수동으로 리페칭 하는게 지금 저희 상황에 딱 맞는 상황 같아보이긴합니다.

그런데 refetch 함수가 props drilling 되는 문제도 그렇고, 모아보기 또는 즐겨찾기 버튼을 누르면 queryKey 등을 통해서 해당 버튼 컴포넌트 내부에서 refetch 할 수 있으면 좋겠지만 그런 메서드가 없는 것 같더군요?? (제가 못찾은거일 수 도 있습니다.)

아무튼 자동 refetch 여기 블로그 말대로라면 상태를 넘겨서 선언적으로 갱신시켜라 이런말이 있습니다. 리액트 쿼리에서 강조한다는 내용인데 따로 참고 링크가 없어서 완전히 신뢰하진 않지만 한 번 확인해보는 것도 좋아보입니다.

하지만 결과적으로 봤을 때 본문에도 적어두겠지만 refetch 하는 이유가 유저가 즐겨찾기 하면서 인기있을지도의 순위 변동이 일어나고 그에 따른 최신 업데이트를 위한 것인데 이 과정이 비용이 엄청 들긴합니다. topics 호출을 세 번 하는 셈이니.. 이 부분은 정책적으로 좀 더 고민해봐야할 것 같습니다.

Copy link
Collaborator

Choose a reason for hiding this comment

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

오호.. 세인이 말씀해주셔서 이 부분에 대해서 저는 물러나겠습니다~

queryKey: ['topics', url],
queryFn: () => getTopics(url),
});
return { topics, refetchTopics };
};

export default useTopicsQuery;
17 changes: 11 additions & 6 deletions frontend/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import ReactDOM from 'react-dom/client';
import ReactGA from 'react-ga4';
import { ThemeProvider } from 'styled-components';
Expand All @@ -21,11 +22,15 @@ if (process.env.REACT_APP_GOOGLE_ANALYTICS) {
ReactGA.initialize(process.env.REACT_APP_GOOGLE_ANALYTICS);
}

const queryClient = new QueryClient();
Copy link
Collaborator

Choose a reason for hiding this comment

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

여기 옵션 회의 때 한 번 얘기해봅시다. 윈도우 포커스 될 때 자동 refetch 하는게 기본값 같던데, 그럴 필요까지는 없어보이니..


root.render(
<ThemeProvider theme={theme}>
<GlobalStyle />
<ErrorBoundary fallback={NotFound}>
<App />
</ErrorBoundary>
</ThemeProvider>,
<QueryClientProvider client={queryClient}>
<ThemeProvider theme={theme}>
<GlobalStyle />
<ErrorBoundary fallback={NotFound}>
<App />
</ErrorBoundary>
</ThemeProvider>
</QueryClientProvider>,
);
Loading