본문 바로가기
리액트(React)

리액트로 뉴스뷰어 만들어 보기(3)

by 즐거운코딩 2023. 6. 6.
반응형

뉴스 카테고리는 총 6개 입니다.

  • business(비즈니스)
  • science(과학)
  • entertainment(연예)
  • sports(스포츠)
  • health(건강)
  • technology(과학)

API 카테고리는 영어로 되어 있어 카테고리 이름을 한글로 보여준 뒤 클릭시 영어로된 카테고리 값을 사용하도록 구현합니다.

import styled from 'styled-components';

const categories = [
  {
    name: 'all',
    text: '전체보기'
  },
  {
    name: 'business',
    text: '비즈니스'
  },
  {
    name: 'entertainment',
    text: '연예'
  },
  {
    name: 'health',
    text: '건강'
  },
  {
    name: 'science',
    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.div`
  font-size: 1.125rem;
  cursor: pointer;
  white-space: pre;
  text-decoration: none;
  color: inherit;
  padding-bottom: 0.25rem;

  &:hover {
    color: #495057;
  }

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

const Categories = () => {
  return (
    <CategoriesBlock>
      {categories.map(c => (
        <Category key={c.name}>{c.text}</Category>
      ))}
    </CategoriesBlock>
  );
};

export default Categories;

categories 배열 안에 name (실제 카테고리), text(렌더링할 때 사용할 한글 카테고리)를 넣어주었습니다.

App 컴포넌트를 아래와 같이 수정하면 카테고리 목록이 보입니다.

import Categories from './components/Categories';
import NewsList from './components/NewsList';

function App() {
  return (
    <>
      <Categories />
      <NewsList />
    </>
  );
}

export default App;

이제 카테고리를 선택하면 해당 카테고리 뉴스만 보이도록 만들어 보겠습니다.

App.js 에서 category 상태를 useState로 관리하고 category 값을 업데이트하는 onSelect 함수도 만듭니다.

그리고 category와 onSelct 함수를 Category 컴포넌트에 props로 전달하고, category 값을 NewsList 컴포넌트에도 전달합니다.

import { useCallback, useState } from 'react';
import Categories from './components/Categories';
import NewsList from './components/NewsList';

function App() {
  const [category, setCategory] = useState('all');  // 전체보기를 초기값으로 설정
  const onSelect = useCallback((category) => setCategory(category), []);

  return (
    <>
      <Categories category={category} onSelect={onSelect} />
      <NewsList category={category} />
    </>
  );
}

export default App;

다음으로 Categories 컴포넌트에서는 props로 전달받은 onSelet를 각 Category 컴포넌트의 onClick으로 설정하고, 현재 선택된 카테고리 값에 따라 다른 스타일을 적용 합니다.

import styled, { css } from 'styled-components';

const categories = [
  {
    name: 'all',
    text: '전체보기',
  },
  {
    name: 'business',
    text: '비즈니스',
  },
  {
    name: 'entertainment',
    text: '연예',
  },
  {
    name: 'health',
    text: '건강',
  },
  {
    name: 'science',
    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.div`
  font-size: 1.125rem;
  cursor: pointer;
  white-space: pre;
  text-decoration: none;
  color: inherit;
  padding-bottom: 0.25rem;

  &:hover {
    color: #495057;
  }
  ${(props) =>
    props.active &&
    css`
      font-weight: 600;
      border-bottom: 2px solid #22b8cf;
      color: #22b8cf
      &:hover {
        color: #3c9db;
      }
  `}

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

const Categories = ({ onSelect, category }) => {
  return (
    <CategoriesBlock>
      {categories.map((c) => (
        <Category
          key={c.name}
          active={category === c.name}
          onClick={() => onSelect(c.name)}
        >
          {c.text}
        </Category>
      ))}
    </CategoriesBlock>
  );
};

export default Categories;

Category의 props에 따른 스타일 변경을 위해 styled-components에서 css 를 사용합니다.

넘겨 받은 props인 active가 true 일 경우만 해당 스타일이 적용됩니다.

이제 NewsList 컴포넌트에서 전체보기 대신 선택한 category에 해당되는 뉴스를 불러오는 것을 구현해보겠습니다.

  const NewsList = ({ category }) => {
  const [articles, setArticles] = useState(null);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    // async 를 사용하는 함수를 따로 선언
    const fetchData = async () => {
      setLoading(true);
      try {
        const query = category === 'all' ? '' : `&category=${category}`;
        const response = await axios.get(
          `https://newsapi.org/v2/top-headlines?country=kr${query}&apiKey=1ea3c5d642534097a4ae5a32303b16d8`,
        );
        setArticles(response.data.articles);
      } catch (e) {
        console.log(e);
      }
      setLoading(false);
    };
    fetchData();
  }, [category]);

App.js 에서 NewsList props로 넘겨받은 category 값을 이용해 category 값에 따라서 요청할 주소를 동적으로 바꿔줍니다.

그리고 category가 바뀔 때마다 뉴스를 새로 불러 와야 하기 때문에 useEffect의 의존 배열에 category를 넣어줘야 합니다.

useEffect를 사용하면 컴포넌트가 처음 렌더링될 때, 그리고 category값이 바뀔 때마다 요청하도록 설정 가능합니다.

다음에는 카테고리 값을 useState 대신 리액트 라우터의 URL 파라미터를 사용하여 관리해보겠습니다.

반응형