beecomci / today_i_learned

0 stars 0 forks source link

[React JS 마스터클래스] Chapter #4 CRYPTO TRACKER #28

Open beecomci opened 2 years ago

beecomci commented 2 years ago

📌 react-query

Setup

react-router-dom

📌 Styles

Reset Style

1. styled-reset

2. createGlobalStyle

import { createGlobalStyle } from "styled-components";

const GlobalStyle = createGlobalStyle`
  body {
    color: red;
  }
`;

function App() {
  return (
    <>
      <GlobalStyle />
      <Router />
    </>
  );
}

image

Fragment

Font

Theme

📌 Coins Home

API fetch

// 1. normal
const getCoins = async () => {
    const json = await (
      await fetch("https://api.coinpaprika.com/v1/coins")
    ).json();
};

useEffect(() => getCoins(), []);
// 2. IIFE로 굳이 getCoins 함수를 또 만들 필요 없이 즉시 실행
useEffect(() => {
    (async () => {
      const json = await (
        await fetch("https://api.coinpaprika.com/v1/coins")
      ).json();
    })();
}, []);

📌 Coin Detail - Route States

현재까지 개발된 상황

1. Coins Home 페이지에서 Loading 문구가 최초로 노출됨
2. 그 사이에 API request가 종료되면 코인 리스트를 가져올 수 있게 되고 Loading 문구는 사라지고 코인 리스트 UI 노출
3. 특정 코인을 클릭하면 Coin Detail 페이지로 이동 (ex. /btc)
4. 페이지 이동 후, 코인 name 제목이 여전히 특정 코인 데이터를 가져오는 동안 Loading 문구 노출됨

비하인드 더 씬 데이터

// Coins.tsx
// react-router-dom v6점대 이상부터 아래처럼 사용
<Link to={`/${coin.id}`} state={{ name: coin.name }}>

// Coin.tsx
// Link에서 state로 넘겼던 coin.name을 Coin에서 useLoaction을 사용해서 get 
// react-router-dom v6점대 이상부터 <> generic 미지원, 아래처럼 사용 가능 
interface RouteStates {
  state: {
    name: string;
  };
}

function Coin() {
  const {
      state: { name }
    } = useLocation() as RouteStates;

  return (
    <Container>
      <Header>
        <Title>{name}</Title> // 더이상 useParmas로 가져온 coinId가 아닌 coin의 name을 직접 뿌려줄 수 있음 
      </Header>
    </Container>
  );
}

Uncaught TypeError: Cannot read properties of null (reading 'name')

해결 방안

function Coin() {
  const location = useLocation() as RouteStates; // {... state: null}
  const name = location?.state?.name; // undefined 반환 

  return (
    <Container>
      <Header>
        <Title>{name  "Loading..."}</Title>
      </Header>
      {loading ? <Loader>Loading...</Loader> : null}
    </Container>
  );
}

📌 Coin Detail - Data Types

// 1. 아래 key 복사
temp1 = { API 데이터 };
Object.keys(temp1).join(); // 'id, name, symbol...'

// 2. VSCode에 붙여넣고 Command + D로 콤마 선택해서 삭제 후 엔터
// 전체 영역 선택 후, Command + Shift + i로 각 모든 커서를 받아서 공통 문자 :와 ; 입력

// 3. 아래 values의 type 복사 
Object.values(temp1).map(item => typeof item).join(); // 'string, string, boolean...'

// 4. 2번처럼 콤마 선택해서 삭제 후 엔터

// 5. 2번까지 마친 key를 전체 선택 후, Comand + Shift + i로 각 모든 커서가 나오면 4번 key 붙여넣기 

// 6. But array같은 경우 object로만 type 변환이 되어서 내부 속성들의 타입도 명시해줘야 함
interface ITag {
  coin_counter: number;
  id: string;
  ...
}

interface IInfoData {
  id: string;
  name: string;
  tag: ITag[];
  ...
}

📌 Coin Detail - Nested Routes

Nested Route

// 1. Router.tsx
// v6점대부터 exact는 더이상 사용하지 않고 여려 라우팅을 매칭하고 싶은 경우 URL뒤에 /* 붙임 
<Route path="/:coinId/*" element={<Coin />}></Route>

// 2. Coin.tsx
// v6점대부터 상대 경로 지원 
<Routes>
  <Route path="/price" element={<Price />}></Route>
  <Route path="/chart" element={<Chart />}></Route>
</Routes>

useMatch hook

const Tab = styled.span<{ isActice: boolean }>`
  ...
  color: ${props =>
    props.isActice ? props.theme.accentColor : props.theme.textColor};
`;

function Coin() {
  const priceMatch = useMatch("/:coinId/price"); // 내가 이 url 안에 있냐
  const chartMatch = useMatch("/:coinId/chart");

  return (
  {/* chartMatch가 null이 아님 -> /:coinId/chart url에 들어와있다면 isActice는 true */}
    <Tabs>
      <Tab isActice={chartMatch !== null}>
        <Link to={`/${coinId}/chart`}>Chart</Link>
      </Tab>
      <Tab isActice={priceMatch !== null}>
        <Link to={`/${coinId}/price`}>Price</Link>
      </Tab>
     </Tabs>

     <Routes>
       <Route path="/price" element={<Price />}></Route>
       <Route path="/chart" element={<Chart />}></Route>
     </Routes>
  );
}

📌 React Query

// index.tsx
// 초기 세팅
import { QueryClient, QueryClientProvider } from "react-query";

const queryClient = new QueryClient();

ReactDOM.render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <ThemeProvider theme={theme}>
        <App />
      </ThemeProvider>
    </QueryClientProvider>
  </React.StrictMode>,
  document.getElementById("root")
);

Why use react-query

// api.ts
// json data의 Promise 반환하는 함수 export
// 일반적으로 API fetch 관련된 함수는 컴포넌트에서 분리함
export function fetchCoins() {
  // fetch 후 response의 json return
  return fetch("https://api.coinpaprika.com/v1/coins").then(response =>
    response.json()
  );
}

// Coins.tsx
const { isLoading, data } = useQuery<ICoin[]>("allCoins", fetchCoins);

Devtools

// App.tsx
import { ReactQueryDevtools } from "react-query/devtools";

function App() {
  return (
    <>
      <GlobalStyle />
      <Router />
      <ReactQueryDevtools initialIsOpen={true} />
    </>
  );
}

image

Coin에 react-query 적용

// Coin.tsx
const { coinId } = useParams();
const location = useLocation() as RouteStates;
const name = location?.state?.name;
const priceMatch = useMatch("/:coinId/price"); // 내가 이 url 안에 있냐
const chartMatch = useMatch("/:coinId/chart");

// param1 : 고유한 값 coinId를 query key로 사용
// param2 : Coins에서는 인자가 필요없었으나 여기선 coinId를 알아야 하니까 (호출이 아닌 함수 자체로 넘겨줘야함 !)
// 그래서 fetchCoinInfo 함수를 호출해서 ciondId를 넣어주는 함수를 생성해서 전달

// react query는 query를 array로 봄
// 넘겨주는 key를 배열로 만들어서 같은 coinId를 사용하지 않도록 생성
const { isLoading: infoLoading, data: infoData } = useQuery<IInfoData>(
  ["info", coinId],
  () => fetchCoinInfo(coinId!)
);
const { isLoading: tickersLoading, data: tickersData } = useQuery<IPriceData>(
  ["tickers", coinId],
  () => fetchCoinTickers(coinId!)
);

// 2개 로딩이 끝나지 않으면 Loading 노출 
const loading = infoLoading || tickersLoading;

return (
  <Container>
    <Header>
      {/* state로부터 온 name이 있으면 -> name or 없으면 -> loading */}
      {/* 근데 loading이 true면 Loading 메세지를 or false면 API로부터 온 name  */}
      <Title>{name ? name : loading ? "Loading..." : infoData?.name}</Title>
    </Header>
    {loading ? (
      <Loader>Loading...</Loader>
    ) : ( ...
);

📌 Price Chart

Apexchart.js

react-helmet

import { Helmet, HelmetProvider } from "react-helmet-async";

<HelmetProvider>
  <Helmet>
    <title>{name ? name : loading ? "Loading..." : infoData?.name}</title>
  </Helmet>
</HelmetProvider>
beecomci commented 2 years ago

왜 Coins에서는 Link 클릭시 다 지워지고 Coin이 보여지는데 Coin에서 탭 Link 클릭시에는 하단에 Price, Chart 컴포넌트만 re-render 되는거지?

Link여서? 새로고침이 안되는거라?