App.js
import React, { useCallback, useEffect, useState } from "react";
import "./App.scss";
import MovieList from "./components/MovieList";
import AddMovie from "./components/AddMovie";
function App() {
const [movies, setMovies] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null); // ์๋ฌ ์๋ ์ํ
/*
// ๋น๋๊ธฐโญ
function fetchMovieHandler() {
fetch("https://swapi.dev/api/films/")
.then((response) => {
return response.json(); // json ๋์ ์ฐ๋ฆฌ๊ฐ ์ฌ์ฉํ ์ ์๋ ์๋ฐ์คํฌ๋ฆฝํธ ํํ๋ก ๋ณํ
})
.then((data) => {
const transformedMovies = data.results.map((movieData) => {
return {
id: movieData.episode_id,
title: movieData.title,
openingText: movieData.opening_crawl,
releaseDate: movieData.release_date,
// ํ์ํ 4๊ฐ๋ง ์๋ก์ด ๊ฐ์ฒด๋ก ๋ฐํ
};
});
setMovies(transformedMovies);
});
}
*/
// ๋น๋๊ธฐโ (async, await)
/*
async function fetchMovieHandler() {
setIsLoading(true);
setError(null);
try {
const response = await fetch("https://swapi.dev/api/films/");
if (!response.ok) {
throw new Error("์๋ฌ ๋ฐ์"); // ๋ท๋ถ๋ถ ์ฝ๋ ์งํx
}
const data = await response.json();
const transformedMovies = data.results.map((movieData) => {
return {
id: movieData.episode_id,
title: movieData.title,
openingText: movieData.opening_crawl,
releaseDate: movieData.release_date,
// ํ์ํ 4๊ฐ๋ง ์๋ก์ด ๊ฐ์ฒด๋ก ๋ฐํ
};
});
setMovies(transformedMovies);
} catch(error) {
setError(error.message);
}
setIsLoading(false);
}
*/
// const ํ์์ผ๋ก ๋ณ๊ฒฝ
const fetchMovieHandler = useCallback(async () => {
setIsLoading(true);
setError(null);
try {
const response = await fetch(
"https://movietest-34408-default-rtdb.asia-southeast1.firebasedatabase.app/movie.json"
);
if (!response.ok) {
throw new Error("์๋ฌ ๋ฐ์"); // ๋ท๋ถ๋ถ ์ฝ๋ ์งํx
}
const data = await response.json();
const transformedMovies = data.results.map((movieData) => {
return {
id: movieData.episode_id,
title: movieData.title,
openingText: movieData.opening_crawl,
releaseDate: movieData.release_date,
// ํ์ํ 4๊ฐ๋ง ์๋ก์ด ๊ฐ์ฒด๋ก ๋ฐํ
};
});
setMovies(transformedMovies);
} catch (error) {
setError(error.message);
}
setIsLoading(false);
}, []); // [] - ํ๋ฉด ์ด๋ฆด ๋ ์ฒ์์๋ง ์๋๋จ
// fetchMovieHandler ํจ์๊ฐ ํธ์ถ๋ ๋๋ ๋ฐ์.
// ํจ์๋ ์ธ๋ถ์์ ๋ฐ๋ ์ ์์(ex. ๋ณ์ ๋ฐ์์ด) -> ๋ฌดํ๋ฃจํ ๊ฐ๋ฅ์ฑ ์์
useEffect(() => {
fetchMovieHandler();
}, [fetchMovieHandler]);
// useCallback(()=>{}, [])
// ์ถ๊ฐํ๋ ํจ์
const addMovieHandler = (item) => {
// console.log(item);
fetch(
"https://movietest-34408-default-rtdb.asia-southeast1.firebasedatabase.app/movie.json",
{
method: "POST", // firebase์ ๋ฆฌ์์ค ๋ง๋ฆ
body: JSON.stringify(item), // js ๊ฐ์ฒด๋ฅผ json์ผ๋ก ๋ณํ
// headers: { "Content-Type": "application/json" }, // firebase์์๋ ์๋ต ๊ฐ๋ฅ, API์์ ์ด๋ค ์ปจํ
์ธ ๊ฐ ์ ๋ฌ๋๋์ง ์ ์ ์๊ฒ ํด์ค
}
);
};
let content = <p>NO MOVIES</p>;
if (isLoading) {
content = <p>LOADING...</p>;
}
if (movies.length !== 0) {
content = <MovieList movie={movies} />;
}
if (error) {
content = <p>{error}</p>;
}
return (
<main>
<section>
<AddMovie onAddMovie={addMovieHandler} />
</section>
<section>
<button onClick={fetchMovieHandler}>Fetch Movie</button>
</section>
<section>
{content}
{/* isLoading์ด false์ผ ๋
{!isLoading && <MovieList movie={movies} />}
{!isLoading && movies.length === 0 && <p>NO MOVIES</p>}
{isLoading && <p>LOADING...</p>} */}
</section>
</main>
);
}
export default App;
MovieList.jsx
import React from "react";
import Movie from "./Movie";
const MovieList = (props) => {
return (
// map()์ ์ฌ์ฉํ์ฌ ํ๋ฉด์ ์ถ๋ ฅ
<ul>
{props.movie.map((item) => (
<Movie
key={item.id}
id={item.id}
title={item.title}
releaseDate={item.releaseDate}
openingText={item.openingText}
/>
))}
</ul>
);
};
export default MovieList;
Movie.jsx
import React from "react";
const Movie = (props) => {
return (
<li className="movie">
<h2>{props.title}</h2>
<h3>{props.releaseDate}</h3>
<p>{props.openingText}</p>
</li>
);
};
export default Movie;
AddMovie.jsx
import React, { useRef } from "react";
const AddMovie = (props) => {
const titleRef = useRef("");
const textRef = useRef("");
const dataRef = useRef("");
const submitHandler = (e) => {
e.preventDefault();
const movie = {
title: titleRef.current.value,
openingText: textRef.current.value,
releaseDate: dataRef.current.value,
};
props.onAddMovie(movie);
};
return (
<form onSubmit={submitHandler}>
<div className="control">
<label htmlFor="title">title</label>
<input type="text" id="title" ref={titleRef} />
</div>
<div className="control">
<label htmlFor="text">text</label>
<textarea id="text" rows="5" ref={textRef}></textarea>
</div>
<div className="control">
<label htmlFor="date">date</label>
<input type="text" id="date" ref={dataRef} />
</div>
<button>Add Movie</button>
</form>
);
};
export default AddMovie;
npm์ผ๋ก ์ค์น ๋ pwa๋ npm run dev๋ก ์คํ
๋ฆฌ์์ค์ ์ฌ์ ์บ์ ๊ฐ๋ฅํ ๋ชจ๋์ ๋ง๋ค์ด ์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
module.exports = {
staticFileGlobs: [
"index.html",
"css/*.css",
"img/**.*",
"js/**/*"
],
};
-> service-worker.js ํ์ผ์ด ์๊น
index.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- apple -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<!-- title -->
<title>KAKAO - ๋ก๊ทธ์ธ</title>
<!-- css -->
<link rel="stylesheet" href="./css/style.css" />
<!-- ํ๋น์ฝ -->
<link rel="shortcut icon" href="./img/favicon.png" type="image/x-icon" />
<!-- manifest -->
<link rel="manifest" href="manifest.json" />
<!-- js -->
<script src="./js/main.js"></script>
<script>
if("serviceWorker" in navigator) {
// ๋ธ๋ผ์ฐ์ ์ ์๋น์ค์์ปค๊ฐ ์์ ๋ (์ง์์ฌ๋ถ)
navigator.serviceWorker.register("service-worker.js") // ๋น๋๊ธฐ
.then((success)=>{
console.log("servieceWorker ์ค์น์ ์ฑ๊ณตํ์ต๋๋ค", success);
})
}
</script>
</head>
<body>
<div class="login_container">
<div class="login">
<h1 class="login_logo">์นด์นด์คํก</h1>
<form action="./chatList.html" class="login_form">
<input
type="text"
class="login_form_id"
placeholder="์นด์นด์ค๊ณ์ (์ด๋ฉ์ผ ๋๋ ์ ํ๋ฒํธ)"
/>
<input type="password" class="login_form_pw" placeholder="๋น๋ฐ๋ฒํธ" />
<input type="submit" class="login_form_btn" value="๋ก๊ทธ์ธ" />
<input type="checkbox" class="login_form_check" id="check" />
<label for="check">์๋๋ก๊ทธ์ธ</label>
</form>
<p class="login_account">
<a href="">์นด์นด์ค ๊ณ์ ์ฐพ๊ธฐ</a>
<span>|</span>
<a href="">๋น๋ฐ๋ฒํธ ์ฌ์ค์ </a>
</p>
</div>
</div>
</body>
</html>
sw-config.js
module.exports = {
staticFileGlobs: [
"index.html",
"css/*.css",
"img/**.*",
"js/**.*"
],
};
์์ฑ์ค