TIL | #18 React | Router와 Axios를 이용해 NewsList를

trevor1107·2021년 4월 11일
0

2021-04-08(목)

초기 설정

뉴스 리스트를 화면에 보여주기 위해 우리는 뉴스 API를 사용할 것인데, Axios를 통해서 서버에 요청할 것이다. 이전에 작업한 라우터를 통해서 뉴스의 카테고리도 나눌 예정이라, 라우터 모듈도 추가해주자!
yarn add axios or npm i axios
yarn add react-router-dom or npm i react-router-dom
그리고 스타일 모듈도 설치 yarn add styled-components

뉴스 API

https://newsapi.org/s/south-korea-news-api 링크에 있는 API를 사용하여 예제를 완성할 예정이다.

설계 구조

뉴스 컴포넌트

  • 뉴스 요소의
    NewsItem 컴포넌트
  • 뉴스 요소들을 나열하는
    NewsList 컴포넌트
  • 뉴스의 카테고리 메뉴 설정을 위한
    Categorys 컴포넌트

페이지 컴포넌트

  • 뉴스의 카테고리 메뉴와 뉴스 요소 목록을 출력하는 페이지
    NewsPages 컴포넌트

라이브러리

  • 서버 통신의 비동기적 처리에 대한 편리함을 제공해주는
    usePromise 함수

루트

  • APP.js에서 라우트 설정
  • Index.js에서 라우터 사용 설정

결과물

구현

// components/NewsItem.js
import React from 'react';
import styled from 'styled-components';

const NewsItemBlock = styled.div`
    display: flex;

    .thumbnail {
        margin-right: 1rem;
        img {
            display: block;
            width: 150px;
            height: 100px;
            object-fit: cover;
        }
    }
    .contents {
        h2 {
            margin: 0;
            a {
                color: black;
            }
        }
        p {
            margin: 0;
            line-height: 1.5;
            margin-top: 0.5rem;
            white-space: normal;
        }
    }
    & + & {
        margin-top: 3rem;
    }
`;

const NewsItem = ({ article }) => {
    const { title, description, url, urlToImage } = article;
    return (
        <NewsItemBlock>
            {urlToImage && (
                <div className="thumbnail">
                    <a href={url} target="_blank" rel="noopener noreferrer">
                        <img src={urlToImage} alt="thumbnail" />
                    </a>
                </div>
            )}
            <div className="contents">
                <h2>
                    <a href={url} target="_blank" rel="noopener noreferrer">
                        {title}
                    </a>
                </h2>
                <p>{description}</p>
            </div>
        </NewsItemBlock>
    );
};

export default NewsItem;

API_KEY에 해당하는 부분은 뉴스API 가입을 통해서 API_KEY를 얻어 기입하면 된다

// components/NewsList.js
import React, { useEffect, useState } from 'react';
import NewsItem from './NewsItem';
import styled from 'styled-components';
import axios from 'axios';
import usePromise from '../lib/usePromise';
const NewsListBlock = styled.div`
    box-sizing: border-box;
    padding-bottom: 3rem;
    width: 768px;
    margin: 0 auto;
    margin-top: 2rem;
    @media screen and (max-width: 768px) {
        width: 100%;
        padding-left: 1rem;
        padding-right: 1rem;
    }
`;

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=API_KEY`
        );
    }, [category]);

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


    if (!response) {
        return null;
    }
    //response값이 유효할때
    const { articles } = response.data;
    return (
        //article값이 유효하면
        <NewsListBlock>
            {articles.map((article) => (
                <NewsItem key={article.url} article={article} />
            ))}
        </NewsListBlock>
    );
};
export default NewsList;
// components/Categories.js
import React from 'react';
import styled, { css } from 'styled-components';
import { NavLink } from 'react-router-dom';
const categories = [
    {
        name: 'all',
        text: '전체보기',
    },
    {
        name: 'business',
        text: '비즈니스',
    },
    {
        name: 'entertainment',
        text: '엔터테인먼트',
    },
    {
        name: 'health',
        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;
    }
`;

//styled(컴포넌트)``
const Category = styled(NavLink)`
    font-size: 1.2rem;
    cursor: pointer;
    white-space: pre;
    text-decoration: none;
    color: inherit;
    padding-bottom: 0.3rem;

    &:hover {
        color: #495057;
    }
    &.active {
        font-weight: 600;
        border-bottom: 2px solid #22b8cf;
        color: #22b8cf;
        &:hover {
            color: #3bc9db;
        }
    }
    & + & {
        margin-left: 1rem;
    }
`;
const Categories = () => {
    return (
        <CategoriesBlock>
            {categories.map((c) => (
                <Category
                    key={c.name}
                    //active={category === c.name}
                    // onClick={() => onSelect(c.name)}

                    activeClassName="active"
                    exact={c.name === 'all'}
                    //NavLink로 만들어진 Category컴포넌트에 to값
                    //"/카테고리이름"
                    // /all-> /
                    to={c.name === 'all' ? '/' : `/${c.name}`}
                >
                    {c.text}
                </Category>
            ))}
        </CategoriesBlock>
    );
};

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

export default function usePromise(promiseCreator, deps) {
	const [loading, setLoding] = useState(false); //대기중
	const [resolved, setResolved] = useState(null); //완료
	const [error, setError] = useState(null); //실패

	useEffect(() => {
		const process = async () => {
			setLoding(true);
			try {
				const resolved = await promiseCreator();
				setResolved(resolved);
			} catch (error) {
				setError(error);
			}
			setLoding(false);
		};
		process();
	}, deps);

	return [loading, resolved, error];
}
// pages/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 (
		<>
			{/* 현재 선택된 카테고리 값을 URL파라미터를 통해 사용하므로 
        Categories컴포넌트에서 현재 선택된 카테고리값을 알려줄 필요없다.
        */}
			<Categories />
			<NewsList category={category} />
		</>
	);
};

export default NewsPage;
// App.js

import { Route } from 'react-router-dom';
import NewsPage from './pages/NewsPage';
export const App = () => {
    return <Route path="/:category?" component={NewsPage} />;
};

export default App;
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter } from 'react-router-dom';

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

reportWebVitals();
profile
프론트엔드 개발자

1개의 댓글

comment-user-thumbnail
2021년 4월 15일

좋아요~~!!

답글 달기