NewsItem.js
import React from 'react';
import styled from 'styled-components';
const NewsItemBlock = styled.div`
display: flex;
.thumnail {
margin-right: 1rem;
img {
display: block;
width: 160px;
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="thumnail">
<a href={url} target="_blank" rel="noopener noreferrer">
<img src={urlToImage} alt="thumnail" />
</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;
NewsList.js
import React from 'react';
import styled from 'styled-components';
import NewsItem from './NewsItem';
const NewsListBlock = styled.div`
box-sizing: border-box;
padding-bottom: 3rem;
width: 768px;
margin: 2rem auto 0;
@media (max-width: 768px) {
width: 100%;
padding-left: 1rem;
padding-right: 1rem;
}
`
const sampleArticle = {
title: 'title',
description: 'description',
url: 'http://www.google.com',
urlToImage: 'https://via.placeholder.com/160',
}
const NewsList = () => {
return (
<NewsListBlock>
<NewsItem article={sampleArticle}></NewsItem>
<NewsItem article={sampleArticle}></NewsItem>
<NewsItem article={sampleArticle}></NewsItem>
<NewsItem article={sampleArticle}></NewsItem>
<NewsItem article={sampleArticle}></NewsItem>
</NewsListBlock>
);
};
export default NewsList;
App.js
import NewsList from "./components/NewsList";
function App() {
return (
<NewsList></NewsList>
);
}
export default App;
NewsList.js
import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
import NewsItem from './NewsItem';
import axios from 'axios';
const NewsListBlock = styled.div`
box-sizing: border-box;
padding-bottom: 3rem;
width: 768px;
margin: 2rem auto 0;
@media (max-width: 768px) {
width: 100%;
padding-left: 1rem;
padding-right: 1rem;
}
`;
const NewsList = () => {
const [articles, setAtricles] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
// async, await진행하는 함수 정의
const fetchData = async () => {
setLoading(true);
try {
const response = await axios.get(
'https://newsapi.org/v2/top-headlines?country=kr&apiKey=f7b41351e7e04385b915fc3e9e7a6565',
);
setAtricles(response.data.articles);
} catch (e) {
console.log(e);
}
setLoading(false);
};
fetchData();
}, []);
if (loading) {
return <NewsListBlock>LOADING...</NewsListBlock>;
}
if (!articles) {
return null;
}
return (
<NewsListBlock>
{articles.map((article) => (
<NewsItem key={article.url} article={article}></NewsItem>
))}
</NewsListBlock>
);
};
export default NewsList;
Categories.js
import React from 'react';
import styled, { css } from 'styled-components';
const categories = [
{
name: 'all',
text: '전체보기',
},
{
name: 'business',
text: '비지니스',
},
{
name: 'entertainment',
text: '엔터테인먼트',
},
{
name: 'science',
text: '과학',
},
{
name: 'sports',
text: '스포츠',
},
{
name: 'technology',
text: '기술',
},
];
const CategoriesBlock = styled.div`
display: flex;
padding: 1rem;
width: 768px;
margin: 0 auto;
@media (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: #3bc9db;
}
`}
& + & {
margin-left: 1rem;
}
`;
const Categories = ({ category, handleSelect }) => {
return (
<CategoriesBlock>
{categories.map((c) => (
<Category
key={c.name}
active={category === c.name}
onClick={() => handleSelect(c.name)}
>
{c.text}
</Category>
))}
</CategoriesBlock>
);
};
export default Categories;
NewsList.js
import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
import NewsItem from './NewsItem';
import axios from 'axios';
const NewsListBlock = styled.div`
box-sizing: border-box;
padding-bottom: 3rem;
width: 768px;
margin: 2rem auto 0;
@media (max-width: 768px) {
width: 100%;
padding-left: 1rem;
padding-right: 1rem;
}
`;
const NewsList = ({ category }) => {
const [articles, setAtricles] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
// async, await진행하는 함수 정의
const fetchData = async () => {
setLoading(true);
try {
const queryStr = category === 'all' ? '' : `&category=${category}`;
const response = await axios.get(
`https://newsapi.org/v2/top-headlines?country=kr${queryStr}&apiKey=f7b41351e7e04385b915fc3e9e7a6565`,
);
setAtricles(response.data.articles);
} catch (e) {
console.log(e);
}
setLoading(false);
};
fetchData();
}, [category]);
if (loading) {
return <NewsListBlock>LOADING...</NewsListBlock>;
}
if (!articles) {
return null;
}
return (
<NewsListBlock>
{articles.map((article) => (
<NewsItem key={article.url} article={article}></NewsItem>
))}
</NewsListBlock>
);
};
export default NewsList;
App.js
import { useCallback, useState } from 'react';
import Categories from './components/Categories';
import NewsList from './components/NewsList';
function App() {
const [category, setCategory] = useState('all');
const handleSelect = useCallback((category) => setCategory(category), []);
return (
<>
<Categories category={category} handleSelect={handleSelect}></Categories>
<NewsList category={category}></NewsList>
</>
);
}
export default App;
Index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter } from 'react-router-dom';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>,
);
reportWebVitals();
NewsPage.js
import React from 'react';
import { useParams } from 'react-router-dom';
import Categories from './Categories';
import NewsList from './NewsList';
const NewsPage = () => {
const params = useParams();
const category = params.category || 'all';
return (
<>
<Categories></Categories>
<NewsList category={category}></NewsList>
</>
);
};
export default NewsPage;
Categories.js
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: 'science',
text: '과학',
},
{
name: 'sports',
text: '스포츠',
},
{
name: 'technology',
text: '기술',
},
];
const CategoriesBlock = styled.div`
display: flex;
padding: 1rem;
width: 768px;
margin: 0 auto;
@media (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 = () => {
return (
<CategoriesBlock>
{categories.map((c) => (
<Category
key={c.name}
to={c.name === 'all' ? '/' : `/${c.name}`}
className={({ isActive }) => (isActive ? 'active' : undefined)}
>
{c.text}
</Category>
))}
</CategoriesBlock>
);
};
export default Categories;
App.js
import { Route, Routes } from "react-router-dom"
import NewsPage from "./components/NewsPage";
function App() {
return (
<Routes>
<Route path="/" element={<NewsPage/>}></Route>
<Route path="/:category" element={<NewsPage/>}></Route>
</Routes>
);
}
export default App;