목차
기존에는 카테고리 값을 useState로 관리했다. 카테고리에 따라 다른 뉴스를 보여줘도 같은 주소값이었다. 이번에는 state 대신, 라우터 URL 파라미터를 이용해볼 것이다.
//index.js
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
);
BrowserRouter를 불러와서 App 컴포넌트를 감싸준 것이 전부다.
// 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`는 어떤 주소로 갈지 정해준다.
// 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>
);
};