CoinTrackerApp.js
import { useEffect, useState } from 'react'
function CoinTrackerApp() {
const [loading, setLoading] = useState(true)
const [coins, setCoins] = useState([])
useEffect(() => {
fetch('https://api.coinpaprika.com/v1/tickers?limit=100')
//fetch('https://api.coinpaprika.com/v1/tickers')
.then((response) => response.json())
.then((json) => {
console.log(json)
setCoins(json)
setLoading(false)
})
}, [])
return (
<div>
<h1>The Coins! {loading ? '' : `(${coins.length})`}</h1>
{loading ? <strong>Loding...</strong> : null}
<ul>
{coins.map((coins) => (
<li key={coins.id}>
{coins.name}({coins.symbol}) : ${coins.quotes.USD.price}
<span> </span>
Coin Rank : {coins.rank}
</li>
))}
</ul>
</div>
)
}
export default CoinTrackerApp
coninTrackerChallengeApp.js
import { useEffect, useState } from 'react'
import coinChallenge from './CoinChallenge.module.css'
function CoinTrackerChallengeApp() {
const [loading, setLoading] = useState(true)
const [coins, setCoins] = useState([])
const [limit, setLimit] = useState(100) // `https://api.coinpaprika.com/v1/tickers?limit=${limit}&"es=KRW`
useEffect(() => {
// const interval = setInterval(() => {
// fetch(`https://api.coinpaprika.com/v1/tickers?limit=${limit}&"es=KRW`)
// .then((response) => response.json())
// .then((json) => {
// console.log(json)
// setCoins(json)
// setLoading(false)
// console.log('1')
// })
// }, 1000)
// return () => clearInterval(interval)
fetch(`https://api.coinpaprika.com/v1/tickers?limit=${limit}&"es=KRW`)
.then((response) => response.json())
.then((json) => {
console.log(json)
setCoins(json)
setLoading(false)
console.log('1')
})
}, [limit])
const onClick = () => {
setLimit((current) => current + 100)
}
const onChange = () => {}
return (
<div>
<div className={coinChallenge.title}>
<h1>Cryptocurrency in real time!</h1>
</div>
<div className={coinChallenge.search}>
<form>
<input
type="text"
placeholder="search Cryptocurrency"
onChange={onChange}
></input>
</form>
{loading ? <strong>Loding...</strong> : null}
</div>
<hr></hr>
<div className={coinChallenge.tablecontainer}>
<table>
<thead>
<tr>
<th>순위</th>
<th>종목</th>
<th>기호</th>
<th>가격(KRW)</th>
<th>총시가</th>
<th>거래량(24H)</th>
<th>변동(24H)</th>
<th>변동(7D)</th>
</tr>
</thead>
<tbody>
{coins.map((coins) => (
<tr key={coins.id}>
<td className={coinChallenge.rank}>{coins.rank}</td>
<td className={coinChallenge.dataColorWhite}>
{coins.name.toUpperCase()}
</td>
<td className={coinChallenge.dataColorWhite}>{coins.symbol}</td>
<td className={coinChallenge.dataColorWhite}>
{parseFloat(
coins.quotes.KRW.price.toFixed(3),
).toLocaleString() + '원'}
</td>
<td className={coinChallenge.dataColorWhite}>
{parseFloat(
coins.quotes.KRW.ath_price.toFixed(3),
).toLocaleString() + '원'}
</td>
<td className={coinChallenge.dataColorWhite}>
{parseFloat(
coins.quotes.KRW.volume_24h.toFixed(3),
).toLocaleString() + '개'}
</td>
<td
className={
parseFloat(coins.quotes.KRW.percent_change_24h) < 0
? coinChallenge.dataColorRed
: coinChallenge.dataColorBlue
}
>
{parseFloat(
coins.quotes.KRW.percent_change_24h.toFixed(3),
).toLocaleString()}
</td>
<td
className={
parseFloat(coins.quotes.KRW.percent_change_7d) < 0
? coinChallenge.dataColorRed
: coinChallenge.dataColorBlue
}
>
{parseFloat(
coins.quotes.KRW.percent_change_7d.toFixed(3),
).toLocaleString()}
</td>
</tr>
))}
</tbody>
</table>
</div>
<div className={coinChallenge.moreBtndiv}>
<button className={coinChallenge.moreBtn} onClick={onClick}>
{loading ? <strong>Loding...</strong> : '더 보기'}
</button>
</div>
</div>
)
}
export default CoinTrackerChallengeApp
CoinChallenge.module.css
body {
background-color: #333;
}
.title {
color: white;
display: flex;
justify-content: center;
align-items: center;
margin: 0;
font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif;
}
.search {
display: flex;
justify-content: center;
align-items: center;
}
.search select {
width: 70px;
margin-right: 20px;
}
.tablecontainer {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
/* @media (max-width: 330px) {
.tablecontainer {
flex-direction: column;
}
} */
.tablecontainer table {
border-collapse: collapse;
}
.tablecontainer table thead {
color: wheat;
border-bottom: 2px solid brown;
}
.tablecontainer table th {
padding-bottom: 5px;
}
.tablecontainer table td {
padding-top: 10px;
}
.moreBtndiv {
display: flex;
justify-content: center;
align-items: center;
}
.moreBtn {
background-color: transparent; /* 배경색을 투명하게 설정 */
border: 2px solid brown; /* 테두리 스타일 및 색상 지정 */
color: white;
margin-top: 10px;
padding: 15px;
min-height: 30px;
min-width: 600px;
}
.rank {
color: chocolate;
}
.dataColorWhite {
color: White;
}
.dataColorRed {
color: red;
}
.dataColorBlue {
color: green;
}
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'
import MovieHome from '../src/routes/MovieHome'
import Detail from './routes/Detail'
// react-router사용할 건데 터미널에 npm install react-router-dom을 입력한다.
// movieApp.js는 더이상 영화를 보여주지 않고 router를 render한다.
// 즉 이곳은 url을 바라보면서 해당 url주소에 따라서 MovieHome, Details 보여줄것이다.
function MovieApp() {
//react-router-dom 5버전 -> 버전6 바뀐 부분
// 1. Switch컴포넌트가 Routes컴포넌트로 대체되었습니다.
// Switch -> Routes
// 2. Route컴포넌트 사이에 자식 컴포넌트를 넣지 않고, element prop에 자식 컴포넌트를 할당하도록 바뀌었습니다.
// <Routes>가 하는 일은 Route를 찾는데 Route는 url이다.
// HashRouter는 주소 앞에 #를 붙여서 사용한다. 대부분 BrowserRouter를 사용하기 때문에 참고만 하자.
// Movie 컴포넌트에서 타이틀을 클릭해서 movie페이지로 이동할때 a herf='' 로 이동하게 되면 우리 어플리케이션에 있는 모든 페이지가 리프레시 된다.
// 딱 movie페이지만 렌더링, 리렌더링 되기를 원하는데, 이때 Link를 사용한다. 다시 Movie.js에서 보기
//path="/movie/:id" :id라고 입력해야 페이지넘기면서 데이터를 전달할 수 있다.
// 다시한번 path="/movie/:id" 경로 사이에는 항상 "/"이거 붙여야 한다.
// :variable 즉 :뒤에가 변수이다.
return (
<Router>
<Routes>
<Route path="/movie/:id" element={<Detail />}></Route>
<Route path="/" element={<MovieHome />}></Route>
</Routes>
</Router>
)
}
export default MovieApp
import PropTypes from 'prop-types'
import { Link } from 'react-router-dom'
import '../MovieIntro.css' // 추가된 CSS 파일
// react-router사용할 건데 터미널에 npm install react-router-dom을 입력한다.
// Movie 컴포넌트에서 타이틀을 클릭해서 movie페이지로 이동할때 a herf='' 로 이동하게 되면 우리 어플리케이션에 있는 모든 페이지가 리프레시 된다.
// 딱 movie페이지만 렌더링, 리렌더링 되기를 원하는데, 이때 Link를 사용한다.
// MovieHome에서 id받아서 props에 넘겨주고 여기서 id값을 받아서 Link to로 연동한다.
// 여기서 <Link to={`/movie/${id}`}>{title}</Link> 작성을 해야,
// 부모 컴포넌트의 path="/movie:id" :id라고 입력한 부분이 페이지넘기면서 데이터를 전달할 수 있다.
function Movie({ id, medium_cover_image, title, summary, genres }) {
// return (
// <div>
// <img src={medium_cover_image} alt={title}></img>
// <h2>
// <Link to={`/movie/${id}`}>{title}</Link>
// </h2>
// <p>{summary.slice(0, 300)}...</p>
// <ul>
// {genres.map((g) => (
// <li key={g}>{g}</li>
// ))}
// </ul>
// </div>
// )
return (
<div className="movie-intro">
<img className="movie-intro-image" src={medium_cover_image} alt={title} />
<h2 className="movie-intro-title">
<Link to={`/movie/${id}`}>{title}</Link>
</h2>
<p className="movie-intro-summary">{summary.slice(0, 300)}...</p>
<ul className="movie-intro-genres">
{genres.map((g) => (
<li key={g}>{g}</li>
))}
</ul>
</div>
)
}
// propTypes 선언하는것 잘보자
// 항상 소문자
Movie.propTypes = {
id: PropTypes.number.isRequired,
medium_cover_image: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
summary: PropTypes.string.isRequired,
genres: PropTypes.arrayOf(PropTypes.string).isRequired,
}
export default Movie
import { useEffect, useState } from 'react'
import Movie from '../components/Movie'
import '../MovieHome.css' // 추가된 CSS 파일
// react-router사용할 건데 터미널에 npm install react-router-dom을 입력한다.
function MovieHome() {
const [loading, setLoading] = useState(true)
const [movies, setMovies] = useState([])
// async, await를 사용하자
// https://yts.mx/api/v2/list_movies.json?minimum_rating=9&sort_by=year
const getMovies = async () => {
const response = await fetch(
//`https://yts.mx/api/v2/list_movies.json?minimum_rating=8.5&sort_by=rating&limit=20`,
`https://yts.mx/api/v2/list_movies.json?sort_by=year&limit=21&page=2`,
)
const json = await response.json()
setMovies((current) => json.data.movies)
setLoading((current) => false)
}
useEffect(() => {
getMovies()
}, [])
console.log(movies)
return (
<div>
<h1>MovieApp</h1>
<hr></hr>
{loading ? (
<h1>Loding...</h1>
) : (
<div className="container">
{movies.map((movie) => (
<Movie
key={movie.id}
id={movie.id}
medium_cover_image={movie.medium_cover_image}
title={movie.title}
summary={movie.summary}
genres={movie.genres}
/>
))}
</div>
)}
</div>
)
}
export default MovieHome
// MovieHome에서 <Link to={`/movie/${id}`}>{title}</Link> 작성을 했을때
// 부모 컴포넌트의 path="/movie:id" :id라고 입력한 부분이 Detail페이지넘기면서 데이터를 넘겨주는데
// Detailpage에서 어떻게 id부분만 받을 수 있나?
// 아래와 같이 useParams를 사용하자.
import { useEffect } from 'react'
import { useParams } from 'react-router-dom'
function Detail() {
// path="/movie/:id" 경로 사이에는 항상 "/"이거 붙여야 한다.
// :variable 즉 :뒤에가 변수이다.
// const id = useParams()
// console.log(id.id)
//이 형식의 코드는 현재 라우트의 URL 매개변수를 가져오는 데 사용된다.
// URL이 "/movies/53528"일 때, useParams를 사용하여 id 값을 추출할 때 아래와 같은 형식의 코드로 가져올수있다.
const { id } = useParams()
console.log(id)
// https://yts.mx/api/v2/movie_details.json?movie_id=
const getMovieDetail = async () => {
const response = await fetch(
`https://yts.mx/api/v2/movie_details.json?movie_id=${id}`,
)
const json = response.json()
console.log(json)
}
useEffect(() => {
getMovieDetail()
}, [])
return <h1>Detail</h1>
}
export default Detail
코드트래커 첼린지에서 많이 아쉬웠다.
전체적인 디자인이나 셀렉트 태그로 원,달러,엔,위안화를 선택해서 사용자가 총 몇개를 가질수 있는지 표현 하고 싶었으나 잘 안되었다.
Naver 쇼핑 API를 사용해서 최저가를 표현하는 나만의 프로젝트를 실행했으나 알고 봤더니 클라이언트 자체에서는 네이버API의 내용을 가져올수가 없다(하루 허비함 아...........)
그래서 스프링부트와 연동해서 실행해보니 어느정도 성과가 있었다.
(코인앱첼린지와 마찬가지로 부족한 면이 많았다.)
컴포넌트와 라우트를 사용해서 전반적인 리액트 앱이 어떻게 구동되는지 큰틀을 알게되었다.
리액트 돔을 설치하는등 생각보다 리엑트에 좋은 라이브러리가 많았다.
디자인이 중요하다! 자유자재로 만들수 있어야 한다.
css또한 엄청 중요하다는 것을 알게 되었다.