안녕하세요! delay100입니다. 이번 포스팅에서는 외부 API 이용하여 뉴스 뷰어를 만들어보겠습니다. https://newsapi.org에서 제공하는 데이터를 받아와 만들게 됩니다.
대부분의 설명은 주석으로 달아놓았으니 코드에 대한 설명은 주석을 확인해주세요!
책 리액트를 다루는 기술, 개정판의 14장 내용을 다루고 있습니다.
이번 포스팅의 Github 링크
https://github.com/delay-100/study-react/tree/main/ch14/news-viewer
1. 프로젝트 준비하기
프로젝트 생성 명령어
yarn create react-app news-viewer
cd news-viewer
설치할 라이브러리
yarn add axios
axios?
- 현재 가장 많이 사용되고 있는 자바스크립트 HTTP 클라이언트
- HTTP 요청을 Promise 기반으로 처리함
+Promise, async/await에 대한 개념도 알아야 하는데 여기에 정리했었습니다.
간단한 async/await 에 대한 예제를 보고 갑시다.
- 간단한 async/await 예제 - src/App.js
import { useState } from 'react';
import axios from 'axios';
const App = () => {
const [data, setData] = useState(null);
// const onClick = () => {
// axios
// .get('https://jsonplaceholder.typicode.com/todos/1')
// .then((response) => {
// setData(response.data);
// });
// };
const onClick = async () => {
// 위의 코드에 async/await 적용
try {
const response = await axios.get(
'https://jsonplaceholder.typicode.com/todos/1',
);
setData(response.data);
} catch (e) {
console.log(e);
}
};
return (
<div>
<div>
<button onClick={onClick}>불러오기</button>
</div>
{data && (
<textarea
rows={7}
value={JSON.stringify(data, null, 2)}
readOnly={true}
/>
)}
</div>
);
};
export default App;
App.js를 위의 코드로 바꾸면 불러오기를 클릭했을 때 JSONPlaceholder(https://jsonplaceholder.typicode.com/)에서 제공하는 가짜 API를 호출하고 이에 대한 응답을 컴포넌트 상태에 넣어서 보여줍니다.
+ Prettier 설정
코드 스타일을 깔끔하게 하기 위해 Prettier을 설정합니다.
VSCode에서 f1을 누른 후 format 입력 후 Enter (-> format Document)을 누르면 됩니다. 그 후 /news-viewer의 최상위 디렉터리에 .prettierrc 파일을 생성했습니다.
- .prettier 파일 생성 및 설정 - .prettierrc
{
"singleQuote": true,
"semi": true,
"useTabs": false,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 80
}
+ jsconfig.json 설정
Vs Code에서 파일 자동 불러오기 기능을 잘 활용하고 싶다면 최상위 디렉터리에 jsconfig.json 파일을 만들어줍니다.
- VS Code에서 파일 자동 불러오기 기능 설정 - jsconfig.json
{
"compilerOptions": {
"target": "es6"
}
}
2. newsapi
2-1. newspi API 키 발급받기
newsapi에서 제공하는 API를 사용해 최신 뉴스를 불러온 후 보여주겠습니다. 이를 위해 newsapi(https://newsapi.org/register)에서 회원가입을 한 후, API 키를 발급 받아야합니다.
가입을 완료하면, 자신의 API KEY를 볼 수 있습니다.
2-2. 한국 뉴스 가져오기
South Korea News API(https://newsapi.org/s/south-korea-news-api)에 들어가면 한국 뉴스를 가져오는 API 설명서가 있습니다.
API 주소 형태
1. 전체 뉴스 불러오기
GET https://newsapi.org/v2/top-headlines?country=kr&apiKey=발급받은API키
2. 특정 카테고리 뉴스 불러오기
GET https://newsapi.org/v2/top-headlines?country=kr&category=카테고리명&apiKey=발급받은API키
카테고리명 종류
bussiness, entertainment, health, science, sports, technology
axios.get에 위의 문장(API 주소)을 추가해주었습니다.
- 전체뉴스 불러오기 API 적용- src/App.js
import { useState } from 'react';
import axios from 'axios';
const App = () => {
const [data, setData] = useState(null);
const onClick = async () => {
// 위의 코드에 async/await 적용
try {
const response = await axios.get(
'https://newsapi.org/v2/top-headlines?country=kr&apiKey=발급받은API키',
);
setData(response.data);
} catch (e) {
console.log(e);
}
};
return (
<div>
<div>
<button onClick={onClick}>불러오기</button>
</div>
{data && (
<textarea
rows={7}
value={JSON.stringify(data, null, 2)}
readOnly={true}
/>
)}
</div>
);
};
export default App;
결과를 보면 나오는 값들은 각 뉴스 데이터를 담은 JSON 객체입니다. 객체의 각 필드가 지니고 있는 정보는 아래와 같습니다.
- title: 제목
- description: 내용
- url: 링크
- urlToImage: 뉴스 이미지
3. UI 만들기
설치할 라이브러리
*styled-components에 대한 설명은 여기에 있습니다.
yarn add styled-components
3-1. 뉴스 뷰어 UI
위의 날 것의 JSON 객체를 꾸며봅시다!
src 디렉터리 안에 components 디렉터리를 생성한 뒤 아래의 파일들을 만들어줍시다.
각 파일들 설명
NewsItem: 각 뉴스 정보를 보여주는 컴포넌트
NewsList: API를 요청하고 뉴스 데이터가 들어 있는 배열을 컴포넌트 배열로 변환하여 렌더링해 주는 컴포넌트
- 간단한 뉴스 뷰어 UI - src/components/NewsItem.js
// NewsItem 컴포넌트: 각 뉴스 정보를 보여주는 컴포넌트
// article이라는 객체를 props로 통째로 받아 와서 사용함
import styled from 'styled-components';
const NewsItemBlock = styled.div`
display: flex;
.thumbnail {
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="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;
- 간단한 뉴스 뷰어 UI - src/components/NewsList.js
// NewsList 컴포넌트: API를 요청하고 뉴스 데이터가 들어 있는 배열을 컴포넌트 배열로 변환하여 렌더링해 주는 컴포넌트
import styled from 'styled-components';
import NewsItem from './NewsItem';
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 sampleArticle = {
title: '제목',
description: '내용',
url: 'https://google.com',
urlToImage: 'https://via.placeholder.com/160',
};
const NewsList = () => {
return (
<NewsListBlock>
<NewsItem article={sampleArticle} />
<NewsItem article={sampleArticle} />
<NewsItem article={sampleArticle} />
<NewsItem article={sampleArticle} />
<NewsItem article={sampleArticle} />
<NewsItem article={sampleArticle} />
</NewsListBlock>
);
};
export default NewsList;
- 간단한 뉴스 뷰어 UI - src/App.js
import NewsList from './components/NewsList';
const App = () => {
return <NewsList />;
};
export default App;
이제 2-2. 한국 뉴스 가져오기 처럼 한국 뉴스를 가져와 UI에 적용시켜 보겠습니다.
useEffect에 등록하는 함수는 async로 작성하면 안됩니다!! 그 대신 함수 내부에 async 함수를 따로 만들어줘야 합니다.
- 한국 뉴스 가져와서 UI에 적용시키기 - src/components/NewsList.js
// NewsList 컴포넌트: API를 요청하고 뉴스 데이터가 들어 있는 배열을 컴포넌트 배열로 변환하여 렌더링해 주는 컴포넌트
import { useState, useEffect } 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: 0 auto;
margin-top: 2rem;
@media screen and (max-width: 768px) {
width: 100%;
padding-left: 1rem;
padding-right: 1rem;
}
`;
const NewsList = () => {
const [articles, setArticles] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
// async를 사용하는 함수 따로 선언
const fetchData = async () => {
setLoading(true);
try {
const response = await axios.get(
'https://newsapi.org/v2/top-headlines?country=kr&apiKey=c01822d3afac4eaabbced7ae8f254a9c',
);
setArticles(response.data.articles);
} catch (e) {
console.log(e);
}
setLoading(false);
};
fetchData();
}, []);
// 대기 중일 때
if (loading) {
return <NewsListBlock>대기 중..</NewsListBlock>;
}
// 아직 articles 값이 설정되지 않았을 때
if (!articles) {
return null;
}
// articles 값이 유효할 때
return (
<NewsListBlock>
{articles.map((article) => (
<NewsItem key={article.url} article={article} />
))}
</NewsListBlock>
);
};
export default NewsList;
- 간단한 뉴스 뷰어 UI - src/App.js
- 간단한 뉴스 뷰어 UI - src/components/NewsItem.js
위의 두 코드가 적용된 상태에서의 실행결과입니다.
3-2. 뉴스 카테고리 분류하기
아래의 예시 코드에서 가장 핵심 코드입니다. 물론 styled 코드를 제외하고, 카테고리 동작을 위해 모든 코드가 필요합니다.
- src/components/NewsList.js
...
useEffect(() => {
// async를 사용하는 함수 따로 선언
const fetchData = async () => {
setLoading(true); // 요청이 대기 중일때는 loading 값이 true가 됨
try {
const query = category === 'all' ? '' : `&category=${category}`; // category값이 all 이라면 공백으로 설정, all이 아니면 "&category=카테고리" 형태의 문자열을 만들도록 함
const response = await axios.get(
`https://newsapi.org/v2/top-headlines?country=kr${query}&apiKey=c01822d3afac4eaabbced7ae8f254a9c`, //API 요청 시 주소에 query값 포함시킴
);
setArticles(response.data.articles);
} catch (e) {
console.log(e);
}
setLoading(false); // 요청이 끝나면 loading값이 false값이 됨
};
fetchData();
}, [category]); // category 값이 바뀔 때마다 뉴스를 새로 불러와야 하기 때문에 useEffect의 의존 배열(두 번째 파라미터로 설정하는 배열)에 category를 넣어줘야 함
...
또한 예시에 Hook(useEffect, useState 등)이 많이 사용되는데, 이 곳에 설명이 있습니다.
- 뉴스 카테고리 분류하기 예시 - src/components/Categories.js
import styled, { css } from 'styled-components';
// categories 라는 배열 안에 name과 text 값이 들어가 있는 객체들을 넣어 주어서 한글로 된 카테고리와 실제 카테고리 값을 연결시켜줌
// name: 실제 카테고리 값, text: 렌더링할 때 사용할 한글 카테고리
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: #3bc9db;
}
`}
& + & {
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;
- 뉴스 카테고리 분류하기 예시 - src/components/NewsList.js
// NewsList 컴포넌트: API를 요청하고 뉴스 데이터가 들어 있는 배열을 컴포넌트 배열로 변환하여 렌더링해 주는 컴포넌트
import { useState, useEffect } 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: 0 auto;
margin-top: 2rem;
@media screen and (max-width: 768px) {
width: 100%;
padding-left: 1rem;
padding-right: 1rem;
}
`;
// NewsList 컴포넌트에서 현재 props로 받아 온 category에 따라 카테고리를 지정하여 API 요청
const NewsList = ({ category }) => {
const [articles, setArticles] = useState(null);
const [loading, setLoading] = useState(false); // loading이라는 상태도 관리하여 API 요청이 대기 중인지 판별함
// 컴포넌트가 화면에 보이는 시점에 api를 요청
// useEffect를 사용해 컴포넌트가 처음 렌더링되는 시점에 API를 요청하면 됩니다.
// 주의할 점: useEffect에 등록하는 함수에 async를 붙이면 안 됨!! => useEffect에서 반환해야 하는 값은 뒷정리 함수이기 때문
// useEffect 내부에서 async/await를 사용하고 싶다면 함수 내부에 async 키워드가 붙은 또다른 함수를 만들어서 사용해줘야 합니다.
useEffect(() => {
// async를 사용하는 함수 따로 선언
const fetchData = async () => {
setLoading(true); // 요청이 대기 중일때는 loading 값이 true가 됨
try {
const query = category === 'all' ? '' : `&category=${category}`; // category값이 all 이라면 공백으로 설정, all이 아니면 "&category=카테고리" 형태의 문자열을 만들도록 함
const response = await axios.get(
`https://newsapi.org/v2/top-headlines?country=kr${query}&apiKey=c01822d3afac4eaabbced7ae8f254a9c`, //API 요청 시 주소에 query값 포함시킴
);
setArticles(response.data.articles);
} catch (e) {
console.log(e);
}
setLoading(false); // 요청이 끝나면 loading값이 false값이 됨
};
fetchData();
}, [category]); // category 값이 바뀔 때마다 뉴스를 새로 불러와야 하기 때문에 useEffect의 의존 배열(두 번째 파라미터로 설정하는 배열)에 category를 넣어줘야 함
// 대기 중일 때
if (loading) {
return <NewsListBlock>대기 중..</NewsListBlock>;
}
// 아직 articles 값이 설정되지 않았을 때
// map 함수를 사용하기 전에 꼭 !articles를 조회하여 해당 값이 현재 null이 아닌지 검사해야 함.
// 만약 데이터가 없다면 null에는 map 함수가 없기때문에 렌더링 과정에서 오류가 발생 => 애플리케이션이 제대로 나타나지 않고 흰 페이지만 보임
if (!articles) {
return null;
}
// articles 값이 유효할 때
return (
<NewsListBlock>
{articles.map((article) => (
<NewsItem key={article.url} article={article} />
))}
</NewsListBlock>
);
};
export default NewsList;
- 뉴스 카테고리 분류하기 예시 - src/App.js
import { useState, useCallback } from 'react';
import NewsList from './components/NewsList';
import Categories from './components/Categories';
const App = () => {
const [category, setCategory] = useState('all'); // category 상태를 useState로 관리
const onSelect = useCallback((category) => setCategory(category), []); // category 값을 업데이트하는 함수
return (
<>
<Categories category={category} onSelect={onSelect} />
{/* category와 onSelct 함수를 Categories 컴포넌트에게 props로 전달*/}
<NewsList category={category} />
{/* category를 NewList 컴포넌트에게 props로 전달*/}
</>
);
};
export default App;
4. React Router 적용
리액트의 라우터는 지난 포스팅에서 다뤘었습니다.
위에서는 카테고리의 값을 useState로 관리했는데, 이번에는 이 값을 리액트 라우터의 URL 파라미터로 바꿔보겠습니다.
4-1. 라우터 적용
설치할 라이브러리
yarn add react-router-dom
그리고 index.js를 아래와 같이 설정해줍니다. <App /> 위에 <BrowserRouter>을 추가해주었습니다.
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(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>,
);
reportWebVitals();
src 디렉터리 안에 pages 디렉터리를 생성한 뒤 아래의 파일(pages/.NewsPage.js)을 만들어줍시다.
현재 선택된 category 값을 URL 파라미터를 통해 사용할 것이므로 Categories 컴포넌트에서 현재 선택된 카테고리 값을 알려 줄 필요도 없고, onSelect 함수를 따로 전달해 줄 필요도 없습니다.
- 리액트 라우터 적용 예시 - src/pages/NewsPage.js
import { useParams } from 'react-router-dom';
import Categories from '../components/Categories';
import NewsList from '../components/NewsList';
const NewsPage = () => {
const params = useParams();
// 카테고리가 선택되지 않았으면 기본값 all로 사용
const category = params.category || 'all';
return (
<>
<Categories />
<NewsList category={category} />
</>
);
};
export default NewsPage;
그 후, App.js를 아래 처럼 바꿔줍니다. 기존 방식에서 리액트 라우터 방식으로 바꿔주는 작업입니다.
- 리액트 라우터 적용 예시 - src/App.js
import { Route, Routes } from 'react-router-dom';
import NewsPage from './pages/NewsPage';
const App = () => {
return (
<Routes>
<Route path="/" element={<NewsPage />} />
<Route path="/:category" element={<NewsPage />} />
</Routes>
);
};
export default App;
이렇게만 바꾸면 제대로 실행되지 않습니다. 여기서 NavLink를 적용해줘야 합니다.
4-2. NavLink 적용
이제 Categories에서 기존의 onSelect 함수를 호출해 카테고리를 선택하고, 선택된 카테고리에 다른 스타일을 주는 기능을 NavLink로 대체하는 작업을 해봅시다.
div, a, button, input 처럼 일반 HTML 요소가 아닌 특정 컴포넌트에 styled-components 사용 시 styled(컴포넌트이름)``과 같은 형식을 사용합니다.
- 리액트 라우터 적용 예시 - src/components/Categories.js
// import styled, { css } from 'styled-components';
import styled from 'styled-components';
import { NavLink } from 'react-router-dom';
// categories 라는 배열 안에 name과 text 값이 들어가 있는 객체들을 넣어 주어서 한글로 된 카테고리와 실제 카테고리 값을 연결시켜줌
// name: 실제 카테고리 값, text: 렌더링할 때 사용할 한글 카테고리
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;
}
`;
// NavLink을 이용해 선택된 카테고리에 다른 스타일을 주는 기능을 적용
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}
className={({ isActive }) => (isActive ? 'active' : undefined)}
to={c.name === 'all' ? '/' : `/${c.name}`}
>
{c.text}
</Category>
))}
</CategoriesBlock>
);
// return 의 Category안에 있어야 하는 내용임
// NavLink로 만들어진 Category 컴포넌트에 to값은 "/카테고리이름" 으로 설정, 전체보기의 경우에는 "/all" 대신 "/"로 설정
};
export default Categories;
실행 결과는 3-2. 뉴스 카테고리 분류하기 예시 실행 결과와 같습니다.
5. 코드 간결화
5-1. 커스텀 Hook
usePromise라는 커스텀 Hook을 만들어봅시다.
src 디렉터리 안에 components 디렉터리를 생성한 뒤 usePromise.js 파일들을 만들어줍시다.
프로젝트의 여러 곳에서 사용될 수 있는 유틸 함수들은 보통 src/lib 디렉터리 내에 작성합니다.
- usePromise 라는 Custom Hook 생성 - src/lib/usePromise.js
import { useState, useEffect } from 'react';
// usePromise Hook: 대기 중, 완료 결과, 실패 결과에 대한 상태를 관리함
// usePromise의 의존 배열 deps를 파라미터로 받아옴
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();
// deps 배열은 usePromise 내부에서 사용한 useEffect의 의존 배열로 설정되는데, 이 배열을 설정하는 부분에 ESLint 경고가 나타남. 아래의 주석은 ESLint 규칙을 비활성화 시키는 주석임
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps);
return [loading, resolved, error];
}
- usePromise 라는 Custom Hook 생성 - src/components/NewList.js
// NewsList 컴포넌트: API를 요청하고 뉴스 데이터가 들어 있는 배열을 컴포넌트 배열로 변환하여 렌더링해 주는 컴포넌트
import styled from 'styled-components';
import NewsItem from './NewsItem';
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;
}
`;
// NewsList 컴포넌트에서 현재 props로 받아 온 category에 따라 카테고리를 지정하여 API 요청
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=c01822d3afac4eaabbced7ae8f254a9c`,
);
}, [category]);
// 대기 중일 때
if (loading) {
return <NewsListBlock>대기 중..</NewsListBlock>;
}
// 아직 response(articles) 값이 설정되지 않았을 때
// map 함수를 사용하기 전에 꼭 !articles를 조회하여 해당 값이 현재 null이 아닌지 검사해야 함.
// 만약 데이터가 없다면 null에는 map 함수가 없기때문에 렌더링 과정에서 오류가 발생 => 애플리케이션이 제대로 나타나지 않고 흰 페이지만 보임
if (!response) {
// usePromise 미사용시 articles로 변경
return null;
}
if (error) {
return <NewsListBlock>에러 발생!</NewsListBlock>;
}
// articles 값이 유효할 때
const { articles } = response.data;
return (
<NewsListBlock>
{articles.map((article) => (
<NewsItem key={article.url} article={article} />
))}
</NewsListBlock>
);
};
export default NewsList;
읽어주셔서 감사합니다. 잘못된 정보는 댓글로 알려주세요!
'Study > React' 카테고리의 다른 글
16. Redux(리덕스) 기본 (0) | 2022.07.31 |
---|---|
15. Context API (0) | 2022.07.30 |
13. Router(라우터) & SPA(Single Page Application) (0) | 2022.07.24 |
12. 불변성 유지하기(immer) (0) | 2022.07.21 |
11. 컴포넌트 성능 최적화 (0) | 2022.07.20 |