저번 시간 Todo 리스트의 백엔드를 구현 했다.
https://velog.io/@frookie/ExpressJS
이번 시간에는 이와 연결할 프론트엔드를 구현해보자.
import CreateToDo from "./components/CreateToDo";
import ToDoCard from "./components/ToDoCard";
function App() {
return (
<div className="min-h-screen flex flex-col justify-start items-center pt-16">
<h1 className="text-4xl font-bold">MY TO DO LIST 😎</h1>
<div>
<div className="mt-8 text-sm font-semibold">Believe in your self</div>
<div className="text-xs">자신을 믿어라</div>
</div>
<CreateToDo />
<ul className="mt-16 flex flex-col w-1/2">
<ToDoCard />
</ul>
</div>
);
}
export default App;
=> ToDo를 입력 및 생성
과 ToDo 카드
컴포넌트로 만들어 삽입
//ToDo 생성창
function CreateToDo() {
return (
<form className="flex mt-2">
<input
className="grow border-2 border-slate-400 rounded-lg focus:outline-slate-700 px-2 py-1 text-lg"
type="text"
/>
<input
className="ml-4 px-2 py-1 bg-slate-400 hover:bg-slate-700 rounded-lg text-slate-50 cursor-pointer"
type="submit"
value="Create"
/>
</form>
);
}
export default CreateToDo;
=> Enter
키를 사용할 수 있도록 input
의 submit
을 이용
function ToDoCard() {
return (
<>
<div className="flex my-4">
<div className="border-4 border-cyan-700 w-8 h-8 rounded-xl"></div>
<div className="text-2xl ml-4">To Do Title</div>
<button className="ml-10 text-gray-400 text-xl hover:text-black hover:scale-110">
X
</button>
</div>
<div className="flex my-4">
<div className="relative">
<div className="border-4 border-cyan-700 w-8 h-8 rounded-xl"></div>
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-cyan-700 w-8 h-8 rounded-xl"></div>
</div>
<div className="text-2xl ml-4">To Do Title</div>
<button className="ml-10 text-gray-400 text-xl hover:text-black hover:scale-110">
X
</button>
</div>
</>
);
}
export default ToDoCard;
=> 위 div
는 끝내지 않은 일(isDone), 아래 div
는 끝낸 일(!isDone)
const [toDoList, setToDoList] = useState();
const getToDoList = async () => {
try {
const response = await axios.get(
`${process.env.REACT_APP_BACKEND_URL}/todo`
);
//.env 파일 만들면 리액트 서버 재시작
if (response.status !== 200) {
alert("데이터를 불러오지 못했습니다.");
return;
}
//api를 통해 응답 받은 데이터를 return에 적용하기 위해
setToDoList(response.data);
} catch (error) {
console.error(error);
}
};
//처음 시작할 때 backend.json 파일에 있는 데이터 불러오기
useEffect(() => {
getToDoList();
}, []);
=> get으로 리스트 불러오기
=> 처음 마운트 되면 toDoList를 불러오기 위해 useEffect
사용해서 불러온다
하지만, 여기서 그대로 진행할 시, CORS(Axios Error)가 뜨게 된다
http 주소는 각각의 구성요소가 하나로 합쳐진 형태이다
- https:// - Protocol
- api.openweathermap.org - Host
- data/2.5/weather - Path
- lat={_lat}&lon={_lon}&appid=${process.env.REACT_APP_WEATHER_API}&units=metric - Query String
(생략되어 있지만 http - 80, https - 443 Port가 포함)웹에서는 https://api.openweathermap.org:443 프로토콜과 호스트가 포함된 경로를 같은 출처라고 인식한다
같은 출처를 가진 곳에서만 리소스를 요청할 수 있는 정책이다.
SOP
가 탄생교차 출처(다른 출처)간의 리소스를 공유할 수 있는 정책이다.
이를 해결하기 위해 CORS 라이브러리를 설치 후 Express 미들웨어로 추가해야 한다. (이때의 CORS는 정책의 CORS가 아니라 NodeJS의 패키지 중 하나인 CORS이다 - 동일이름)
npm i cors
const cors = require("cors")
app.use(cors());
이는 모든 요청에 대해 CORS
를 허용한다. 제한적으로만 허용하려면 다음과 같이 작성해야 한다.
app.use(
cors({
origin: "http://localhost:3000", ▶본인의 경로
credentials: true,
})
);
다시 App.jsx로 돌아가서
<ul className="mt-16 flex flex-col w-1/2">
{toDoList &&
toDoList.map((v, i) => {
return <ToDoCard />;
})}
</ul>
toDoList는 반복 되므로 map
함수로 ToDoCard
가 여러개 나오도록 처리
import axios from "axios";
import { useState } from "react";
//ToDo 생성창
function CreateToDo({ setToDoList }) {
const [title, setTitle] = useState("");
//submit 해서 backend로 보내기
const onSubmitAdd = async (e) => {
try {
e.preventDefault();
if (!title) {
alert("타이틀을 입력해주세요!");
return;
}
const response = await axios.post(
`${process.env.REACT_APP_BACKEND_URL}/todo`,
{
title,
desc: `${title} 화이팅`,
// 임시
}
);
if (response.status !== 200) {
alert("요청을 불러오지 못했습니다");
return;
}
//props로 불러와 검색하고 자동으로 새로고침하기 위해 서버에 있는 데이터 볼러오기
setToDoList();
setTitle("");
} catch (error) {
console.error(error);
}
};
return (
<form className="flex mt-2" onSubmit={onSubmitAdd}>
<input
className="grow border-2 border-slate-400 rounded-lg focus:outline-slate-700 px-2 py-1 text-lg"
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<input
className="ml-4 px-2 py-1 bg-slate-400 hover:bg-slate-700 rounded-lg text-slate-50 cursor-pointer"
type="submit"
value="Create"
/>
</form>
);
}
export default CreateToDo;
=> backend
로 생성해 보내기
=> Input
창에 넣은 값을 인식하도록 "onChange" 사용
=> submit
을 사용해서 새로고침 방지해서 title
내용 넣기
=> 이후 다시 getToDoList
불러와서 다시 자동으로 리스트 새로고침 적용
=> 이후 ToDoCard
를 설정해줘야 한다
인자들을 ToDoCard.jsx에 넣어줘야 리스트가 보임
<ul className="mt-16 flex flex-col w-1/2">
{toDoList && toDoList.map((v, i) => {
return <ToDoCard key={i} isDone={v.isDone} title={v.title} />;
})}
</ul>
=> map
함수를 써서 파라미터를 v.~
로 내려줘야 함
function ToDoCard({ isDone, title }) {
return (
<div className="flex my-4">
{isDone ? (
<>
<div className="relative">
<div className="border-4 border-cyan-700 w-8 h-8 rounded-xl"></div>
<div
onClick={onClickIsDone}
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-cyan-700 w-8 h-8 rounded-xl"
></div>
</div>
<div className="text-2xl ml-4 line-through">{title}</div>
</>
) : (
<>
<div
className="border-4 border-cyan-700 w-8 h-8 rounded-xl"
onClick={onClickIsDone}
></div>
<div className="text-2xl ml-4">{title}</div>
</>
)}
<button
onClick={onClickDelete}
className="ml-10 text-gray-400 text-xl hover:text-black hover:scale-110"
>
X
</button>
</div>
);
}
=>isDone
과 title
을 구조 분해로 받아 서 사용
=>isDone
이 true이면 색이 채워진 것, false면 비워진 것으로 삼항연산자 처리
토글 기능을 추가하기
return (
<ToDoCard
key={i}
isDone={v.isDone}
title={v.title}
index={i}
getToDoList={getToDoList}
/>
);
=> 인자로 map함수의 i
가 index
기 때문에 :id
역할과 같으므로 내려준다
=> getToDoList
는 마찬가지로 자동으로 목록을 새로고침 하기 위해 내려준다
function ToDoCard({ isDone, title, index, getToDoList }) {
const onClickToggle = async () => {
try {
const response = await axios.put(
`${process.env.REACT_APP_BACKEND_URL}/todo/done/${index}`
);
if (response.status !== 200) {
alert("요청을 불러오지 못했습니다");
return;
}
getToDoList();
} catch (error) {
console.error(error);
}
};
=> 인자로 내려받은 index
와 getToDoList
를 이용해 클릭하면 체크박스 채워지도록 설정
=>여기서 경로에 주의. /todo/done/${index}
const onClickDelete = async () => {
try {
const response = await axios.delete(
`${process.env.REACT_APP_BACKEND_URL}/todo/${index}`
);
if (response.status !== 200) {
alert("요청을 불러오지 못했습니다");
return;
}
getToDoList();
} catch (error) {
console.error(error);
}
};
반복되지만 주소 /todo/${index}
에 주의