[말로 풀어쓴 React] 외부 API를 연동하여 뉴스뷰어 만들기 2

DongGu·2021년 1월 22일
0

목차

  • 리액트 라우터 적용하기
  • usePromise 커스텀 Hook 만들기

1. 리액트 라우터 적용하기

기존에는 카테고리 값을 useState로 관리했다. 카테고리에 따라 다른 뉴스를 보여줘도 같은 주소값이었다. 이번에는 state 대신, 라우터 URL 파라미터를 이용해볼 것이다.

1) 리액트 라우터 적용

//index.js
ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById('root')
);

BrowserRouter를 불러와서 App 컴포넌트를 감싸준 것이 전부다.

2) NewsPage 생성

// page/NewsPages.js
import React from 'react';
import Categories from '../components/Categories';
import NewsList from '../components/NewsLIst';

const NewsPage = ({ match }) => {
  const category = match.params.category || 'all';
  return (
    <>
      <Categories />
      <NewsList category={category} />
    </>
  );
};

export default NewsPage;

category가 정해졌으면 그 것을 값으로 하고, 없으면 기본 값으로 all으로 쓴다. match.params.category의 category는 언제 설정됐을까?
현재 선택된 category값을 url 파라미터로 쓸 것이기 때문에, Categories 컴포넌트에서 현재 선택된 카테고리를 알려 줄 필요도, onSelect를 따로 전달해줄 필요도 없다.

//App.js
import React from 'react';
import { Route } from 'react-router-dom';
import NewsPage from './pages/NewsPage';

const App = () => {
  return <Route path="/:category?" component={NewsPage} />;
};

export default App;

category?에서 ?(물음표)가 들어있다. 이는 category가 선택적이라는 의미로, 있을 수도 없을 수도 있다는 것이다. 일반적으로 아무 것도 없을 때는 모든 종류를 보여준다.

import React from 'react';
import styled from 'styled-components';
import { NavLink } from 'react-router-dom';

const categories = [
  {
    name: 'all',
    text: '전체보기',
  },
  {
    name: 'business',
    text: '비즈니스',
  },
  {
    name: 'entertainment',
    text: '엔터테인먼트',
  },
  {
    name: 'health',
    text: '건강',
  },
  {
    name: 'scinece',
    text: '과학',
  },
  {
    name: 'sports',
    text: '스포츠',
  },
  {
    name: 'technology',
    text: '기술',
  },
];

const CategoriesBlock = styled.div`
  display: flex;
  padding: 1rem;
  width: 768px;
  margin: 0 auto;
  @media screen and (max-width: 768px) {
    width: 100%;
    overflow-x: auto;
  }
`;

const Category = styled(NavLink)`
  font-size: 1.125rem;
  cursor: pointer;
  white-space: pre;
  text-decoration: none;
  color: inherit;
  padding-bottom: 0.25rem;

  &:hover {
    color: #495057;
  }

  &.active {
    font-weight: 600;
    border-bottom: 2px solid #22b8cf;
    color: #22b8cf;
    &:hover {
      color: #3bc9db;
    }
  }

  & + & {
    margin-left: 1rem;
  }
`;

const Categories = ({ onSelect, category }) => {
  return (
    <CategoriesBlock>
      {categories.map((c) => (
        <Category
          key={c.name}
          activeClassName="active"
          exact={c.name === 'all'}
          to={c.name === 'all' ? '/' : `/${c.name}`}
        >
          {c.text}
        </Category>
      ))}
    </CategoriesBlock>
  );
};

export default Categories;

NavLink 컴포넌트는 선택된 값을 선택되지 않은 값과 다르게 보여준다. styled-components를 사용할 때는 styled(컴포넌트 이름)'' 형식을 쓴다. (ex. styled(NavLink)``)

exact는 true일 때 정확히 '/'로 들어왔을 때만 해당하는 라우트들을 보여준다. 이것을 설정해놓지 않으면 '/example'일 때, '/', '/example인 경우 모두 보여준다. 여기선 'all'일 때 전부 보여주기 때문에 저렇게 설정을 했다. to`는 어떤 주소로 갈지 정해준다.

2. usePromise 커스텀 Hook 만들기

// lib/usePromise.js
import { useState, useEffect } from 'react';

export default function usePromise(promiseCreator, deps) {
  const [loading, setLoading] = useState(false);
  const [resolved, setResolved] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    const process = async () => {
      setLoading(true);
      try {
        const resolved = await promiseCreator();
        setResolved(resolved);
      } catch (e) {
        setError(e);
      }
      setLoading(false);
    };
    process();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, deps);
  return [loading, resolved, error];
}

const resolved = await promiseCreator()에서의 promiseCreator는 usePromise의 첫 번째 파라미터로 함수다. NewsList 컴포넌트에서 promiseCreator는 아래와 같다. axios.get(~)이 const resolved의 값이 된다. 뉴스 정보를 다받았을 땐(async-await 는 비동기작업이 완료될 때까지 기다린다), setResolved가, 로딩 전까지는 setLoading(true)까지만 작동된다. 로딩에 실패하면 setError(e)가 작동한다.

() => {
    const query = category === 'all' ? '' : `&category=${category}`;
    return axios.get(
      `https://newsapi.org/v2/top-headlines?country=kr${query}&apiKey=bd27922d5692413bad59ef0ca957c20b`
    );
  }
const NewsList = ({ category }) => {
  const [loading, response, error] = usePromise(() => {
    const query = category === 'all' ? '' : `&category=${category}`;
    return axios.get(
      `https://newsapi.org/v2/top-headlines?country=kr${query}&apiKey=bd27922d5692413bad59ef0ca957c20b`
    );
  }, [category]);

  if (loading) {
    return <NewsListBlock>대기 중...</NewsListBlock>;
  }

  if (!response) {
    return null;
  }

  if (error) {
    return <NewsListBlock>에러 발생!</NewsListBlock>;
  }
  const { articles } = response.data;
  return (
    <NewsListBlock>
      {articles.map((article) => (
        <NewsItem key={article.url} article={article} />
      ))}
    </NewsListBlock>
  );
};
profile
코딩하는 신방과생

0개의 댓글