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

[4주차 기본/심화/생각 과제] #18

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open

[4주차 기본/심화/생각 과제] #18

wants to merge 13 commits into from

Conversation

YOOJS1205
Copy link
Collaborator

@YOOJS1205 YOOJS1205 commented Nov 7, 2022

✨ 구현 기능 명세

기본 과제

  • react-router-dom을 이용해 url에 따른 컴포넌트를 나타낸다.
    image

  • 검색 history를 구현한다.

  • Loading을 나타내는 state혹은 suspense를 추가하여 로딩 중을 나타내는 UI를 구성한다.

  • 유저 정보 박스 내에 닫기 버튼을 구현하여, 검색 전 상태로 되돌리도록 구현한다.
    image
    image

심화 과제

  • msw를 이용하여 팀원의 명단을 불러오는 기능을 구현하였습니다.

생각 과제

https://github.com/IN-SOPT-WEB/JunSangYOO/tree/week4/week4


🎁 PR Point

1. 사용자가 입력한 인풋값을 useNavigate를 통하여 다른 컴포넌트로 전달하였습니다.

// src/components/Finder.jsx
import { useNavigate } from 'react-router-dom';

export default function Finder() {
  const navigate = useNavigate();

  const onSearchUser = async (e) => {
    if (e.key === "Enter") {
      setHistory((prev) => {
        if (prev.indexOf(e.target.value) === -1) {
          return [...prev, e.target.value];
        } else {
          return prev;
        }
      });
      navigate(`/search/${e.target.value}`, {
        state: {
          name: e.target.value,
        },
      });
    }
  };
// 비동기 통신 코드
// src/api/userAPI.js
export const getUserAPI = async (username) => {
  try {
    const res = await axios.get(`https://api.github.com/users/${username}`);
    return res.data;
  } catch (error) {
    console.error(error);
  }
};
  • useGetUserInfo 커스텀 훅을 만들어서 쿼리로 관리하였습니다.
  • 후에 Error, Loading 핸들링이 훨씬 용이할 것 같아서 react-query를 활용하였습니다.
// custom hook
import { useQuery } from "@tanstack/react-query";
import { getUserAPI } from "./userAPI";

export default function useGetUserInfo(userId) {
  const { isLoading, isError, data } = useQuery(
    ["userinfo", userId],
    () => getUserAPI(userId),
    {
      refetchOnWindowFocus: false,
      retry: 0,
    }
  );
  return data;
}
// 데이터 가져오기
// src/components/Profile.jsx
const location = useLocation();
const userInfo = useGetUserInfo(location.state.name);

2. 최근 검색어 기능 구현

  • 상태 관리 라이브러리인 recoil을 활용하였습니다.
  • localStorageuseRecoilState를 이용하여 구현하였습니다.
import React from "react";
import { QueryClientProvider, QueryClient } from "@tanstack/react-query";
import { RecoilRoot } from "recoil";

import Router from "./pages/Router";

export default function App() {
  const queryClient = new QueryClient();
  return (
    <QueryClientProvider client={queryClient}>
      <RecoilRoot>
        <Router />
      </RecoilRoot>
    </QueryClientProvider>
  );
}
const historyState = atom({
  key: `historyState${v1()}`,
  default: JSON.parse(localStorage.getItem("history") || "[]"),
});

// useRecoilState로 history 상태 관리
const [history, setHistory] = useRecoilState(historyState);

// 최근 검색어가 변경될 때 localStorage에 반영
  useEffect(() => {
    localStorage.setItem("history", JSON.stringify(history));
  }, [history]);

// 최근 검색어 기록 삭제
const onClickSearchHistory = async (e) => {
    navigate(`/search/${e.target.textContent.slice(0, -1)}`, {
      state: {
        userInfo: e.target.textContent.slice(0, -1),
      },
    });
  };

3. 로딩중 상태 처리

  • react-querysuspense, lazy의 조합으로 선언적으로 처리하였습니다.
import React from "react";

import Finder from "../components/Finder";
import SuspenseContainer from "../components/SuspenseContainer";

export default function UserInfo() {
  return (
    <>
      <Finder />
      <SuspenseContainer />
    </>
  );
}
import React, { Suspense } from "react";

import LoadingPage from "./LoadingPage";

const ProfileComponent = React.lazy(() => import("./Profile"));

export default function SuspenseContainer() {
  return (
    <Suspense fallback={<LoadingPage />}>
      <ProfileComponent />
    </Suspense>
  );
}

4. 팀원 명단 불러오기

  • mswreact-query를 활용하여 팀원 명단을 불러오는 코드를 작성하였습니다.
// src/mocks/browser.js
import { setupWorker } from "msw";
import { handlers } from "./handlers";

export const worker = setupWorker(...handlers);
// src/mocks/handlers.js
import { rest } from "msw";

export const handlers = [
  rest.get("/user/team", (req, res, ctx) => {
    return res(
      ctx.status(200),
      ctx.json({
        name: ["YOOJS1205", "henization", "iamphj3", "seobbang"],
      })
    );
  }),
];
// src/api/useGetTeamInfo.jsx
import { useQuery } from "@tanstack/react-query";
import { getMyTeamAPI } from "./userAPI";

export default function useGetTeamInfo() {
  const { isLoading, isError, data, refetch } = useQuery(
    ["teaminfo"],
    getMyTeamAPI,
    {
      refetchOnWindowFocus: false,
      retry: 0,
      enabled: false,
    }
  );
  return { data, refetch };
}
// src/components/Team.jsx
import React from "react";
import styled from "styled-components";
import { useNavigate } from "react-router-dom";
import useGetTeamInfo from "../api/useGetTeamInfo";

import Container from "./Container";

export default function Team() {
  const navigate = useNavigate();
  const { data, refetch } = useGetTeamInfo();

  const onClickMemberButton = (e) => {
    if (e.target.nodeName.toLowerCase() === "button") {
      navigate(`/search/${e.target.innerText}`, {
        state: {
          name: e.target.innerText,
        },
      });
    }
    console.log(e.target);
  };
  return (
    <Container>
      <GetButton onClick={refetch}>우리팀보기!</GetButton>
      {data && (
        <TeamBox onClick={onClickMemberButton}>
          {data.name.map((item) => (
            <GetButton key={item}>{item}</GetButton>
          ))}
        </TeamBox>
      )}
    </Container>
  );
}

😭 소요 시간, 어려웠던 점

  • 6h
  • 비동기 처리에서 삽질을 꽤 했습니다,,,
  • CSS가 제일 어려워,,,
  • suspense로 로딩 상태 처리하는 방법을 공부했습니다.

😎 구현 결과물

기본과제

ezgif com-gif-maker (6)

심화과제

image

ezgif com-gif-maker (7)

@YOOJS1205 YOOJS1205 self-assigned this Nov 7, 2022
@YOOJS1205 YOOJS1205 changed the title [3주차 기본/심화/생각 과제] [4주차 기본/심화/생각 과제] Nov 8, 2022
const location = useLocation();
return (
<>
<Finder />
Copy link
Member

Choose a reason for hiding this comment

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

Finder 컴포넌트가 Search 컴포넌트에서도 중복해서 나타나는데, App.jsx 에 넣어줘서 공통 컴포넌트로 쓸 수는 없으려나??

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

오 좋은 생각인 것 같다.
아직 완성은 아니구 나도 이 쪽에 고민이 좀 있는 것 같아.
의견 땡큐땡큐!!

Copy link
Member

Choose a reason for hiding this comment

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

오 좋은 생각인 것 같다. 아직 완성은 아니구 나도 이 쪽에 고민이 좀 있는 것 같아. 의견 땡큐땡큐!!

나도 이제 과제 시작해서 컴포넌트 구조 고민중인데 과제 엄청 빨리 했길래 의견 하나 남기고 갑니다.. 빠른과제 굿..

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

나도 과제 다 하면 놀러갈게 ~~~ 굿!

@YOOJS1205 YOOJS1205 linked an issue Nov 9, 2022 that may be closed by this pull request
Copy link
Contributor

@joohaem joohaem left a comment

Choose a reason for hiding this comment

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

이전에 봤던 코드보다 훨씬 깔쌈해졌네 !!! 보기 편해요🤩

코멘트할 부분들 남겨놨어용

Comment on lines +15 to +19
<QueryClientProvider client={queryClient}>
<RecoilRoot>
<Router />
</RecoilRoot>
</QueryClientProvider>
Copy link
Contributor

Choose a reason for hiding this comment

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

간지 ㅋ ㅋ

}

export default function App() {
const queryClient = new QueryClient();
Copy link
Contributor

Choose a reason for hiding this comment

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

요거 컴포넌트 밖에 빼줘도 되지 않을까욥

Comment on lines +8 to +12
{
refetchOnWindowFocus: false,
retry: 0,
enabled: false,
}
Copy link
Contributor

Choose a reason for hiding this comment

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

중복되는 옵션은 최상단에서도 default option으로 처리해줄 수 있어요!
🔗 공식문서

export default function useGetTeamInfo() {
const { isLoading, isError, data, refetch } = useQuery(
["teaminfo"],
getMyTeamAPI,
Copy link
Contributor

Choose a reason for hiding this comment

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

같은 get api 함순데 여기는 함수호출이고, 아래서는 화살표 함수를 사용하네요 !
통일성 있게 호출하는 것이 좋아보입니다 !

Comment on lines +4 to +5
export default function useGetTeamInfo() {
const { isLoading, isError, data, refetch } = useQuery(
Copy link
Contributor

Choose a reason for hiding this comment

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

해당 훅스는 get 요청의 유틸함수가 담긴 userAPI 과는 다른 폴더에 있는 게 좋을 것 같아요

return res(
ctx.status(200),
ctx.json({
name: ["YOOJS1205", "henization", "iamphj3", "seobbang"],
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
name: ["YOOJS1205", "henization", "iamphj3", "seobbang"],
name: ["YOOJS1205", "henization", "iamphj3", "seobbang", "joohaem"],

Comment on lines +10 to +13
const historyState = atom({
key: `historyState${v1()}`,
default: JSON.parse(localStorage.getItem("history") || "[]"),
});
Copy link
Contributor

Choose a reason for hiding this comment

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

atom 을 관리하는 폴더도 분리하면 좋을 것 같네요!

Copy link
Contributor

Choose a reason for hiding this comment

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

uuid 는 머지요??!

Comment on lines +37 to +43
setHistory((prev) => {
if (prev.indexOf(e.target.value) === -1) {
return [...prev, e.target.value];
} else {
return prev;
}
});
Copy link
Contributor

Choose a reason for hiding this comment

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

히스토리를 어떻게 바꾸는지 방법을 onSearchUser 함수가 알 필요는 없어보입니다.
함수 분리를 해도 좋을 것 같아요

Comment on lines +12 to +13
const onClickMemberButton = (e) => {
if (e.target.nodeName.toLowerCase() === "button") {
Copy link
Contributor

Choose a reason for hiding this comment

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

위임 멋지네ㄷ ㄷ

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Feat: 최근 검색어 기록 추가, 삭제 구현
3 participants