immutable : https://pks2974.medium.com/immutable-js-%EA%B0%84%EB%8B%A8%EC%A0%95%EB%A6%AC-bbd5ad20bbdf
react-router-dom : https://reactrouter.com/docs/en/v6/getting-started/overview
json-server 여는법: npx json-server --watch -p 3333 db.json
import logo from "./logo.svg";
import "./App.css";
import { useState, useEffect } from "react";
import Button from "@mui/material/Button";
import ButtonGroup from "@mui/material/ButtonGroup";
import styled from "styled-components";
import { Link, Routes, Route, useParams, useNavigate } from "react-router-dom";
function Header(props) {
return (
<header className={props.className}>
<h1>
<Link
to="/"
onClick={(evt) => {
props.onSelect();
}}
>
WWW
</Link>
</h1>
</header>
);
}
const HeaderStyled = styled(Header)`
border-bottom: 1px solid gray;
color: red;
`;
function Article(props) {
return (
<article>
<h2>{props.title}</h2>
{props.body}
</article>
);
}
function Nav(props) {
const liTags = props.data.map((e) => {
return (
<li key={e.id}>
<Link
to={"/read/" + e.id}
onClick={(evt) => {
props.onSelect(e.id);
}}
>
{e.title}
</Link>
</li>
);
});
return (
<nav>
<ol>{liTags}</ol>
</nav>
);
}
function Create(props) {
return (
<article>
<h2>Create</h2>
<form
onSubmit={(evt) => {
evt.preventDefault();
const title = evt.target.title.value;
const body = evt.target.body.value;
props.onCreate(title, body);
}}
>
<p>
<input type="text" name="title" placeholder="title"></input>
</p>
<p>
<textarea name="body" placeholder="body"></textarea>
</p>
<p>
<input type="submit" value="Create"></input>
</p>
</form>
</article>
);
}
function Read(props) {
const params = useParams();
const id = Number(params.topic_id);
const [topic, setTopic] = useState({ title: null, body: null });
useEffect(() => {
(async () => {
const resp = await fetch("http://localhost:3333/topics/" + id);
const data = await resp.json();
setTopic(data);
})();
}, [id]);
return <Article title={topic.title} body={topic.body}></Article>;
}
function Control(props) {
const params = useParams();
const id = Number(params.topic_id);
let contextUI = null;
if (id) {
contextUI = (
<>
<Button variant="outlined">Update</Button>
<Button
variant="outlined"
onClick={() => {
props.onDelete(id);
}}
>
Delete
</Button>
</>
);
}
return (
<>
<Button component={Link} to="/create" variant="outlined">
Create
</Button>
{contextUI}
</>
);
}
function App() {
const [mode, setMode] = useState("WELCOME"); // todo 삭제 예정
const [id, setId] = useState(null); // todo 삭제 예정
const [nextId, setNextId] = useState(3);
const [topics, setTopics] = useState([
{ id: 1, title: "html", body: "html is ..." },
{ id: 2, title: "css", body: "css is ..." },
]);
const refreshTopics = async () => {
const resp = await fetch("http://localhost:3333/topics");
const data = await resp.json();
setTopics(data);
};
useEffect(() => {
refreshTopics();
}, []);
const navigate = useNavigate();
return (
<div>
<HeaderStyled onSelect={headerHandler()}></HeaderStyled>
<Nav data={topics} onSelect={navHandler()}></Nav>
<Routes>
<Route
path="/"
element={<Article title="Welcome" body="Hello, WEB!"></Article>}
></Route>
<Route
path="/create"
element={<Create onCreate={onCreateHandler}></Create>}
></Route>
<Route
path="/read/:topic_id"
element={<Read topics={topics}></Read>}
></Route>
</Routes>
<Routes>
{["/", "/read/:topic_id", "/update/:topic_id"].map((path) => {
return (
<Route
key={path}
path={path}
element={
<Control
onDelete={(id) => {
deleteHandler(id);
}}
></Control>
}
></Route>
);
})}
</Routes>
</div>
);
async function onCreateHandler(title, body) {
const resp = await fetch("http://localhost:3333/topics", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ title, body }),
});
const data = await resp.json();
navigate(`/read/${data.id}`);
refreshTopics();
}
function navHandler() {
return (id) => {
setMode("READ");
setId(id);
};
}
function deleteHandler(id) {
const newTopics = topics.filter((e) => {
if (e.id === id) {
return false;
} else {
return true;
}
});
setTopics(newTopics);
navigate("/");
}
function createHandler() {
return () => {
setMode("CREATE");
};
}
function headerHandler() {
return () => {
setMode("WELCOME");
};
}
}
export default App;
import React, { useState, useEffect } from "react";
import axios from "axios";
function Users() {
let [result, setResult] = useState("");
// useEffect를 사용하지 않고 아래를 출력해보세요.
console.log(result);
// axios.get을 호출하고 result에 반환되는 데이터를 저장하세요.
const axiosGet = async () => {
try {
const response = await axios.get('https://reqres.in/api/users/2');
setResult(response.data.data);
} catch(e) {console.log(e)}
}
useEffect(() => {
axiosGet();
})
// then-catch 사용, 정상 작동
// useEffect(() => {
// axios.get("https://reqres.in/api/users/2")
// .then(res => setResult(res.data.data));
// }, []);
// 잘못된 예 1
// useEffect(async () => {
// try{
// const result = await axios.get('https://reqres.in/api/users/2');
// setResult(result.data.data);
// } catch(err) {
// console.log(err);
// }
// }, [])
// 잘못된 예 2
// useEffect(() => {
// axios.get("https://reqres.in/api/users/2")
// .then(res => setResult(res.data.data));
// });
return (
<div>
<h4>React Axios로 HTTP GET 요청하기</h4>
<div>
{/* result를 이용해 출력 결과와 동일하게 출력하세요. */}
<p>Name: {result.first_name} {result.last_name}</p>
<p>Email: {result.email}</p>
</div>
</div>
);
}
export default Users;
axios는 비동기적 함수이므로 then catch 혹은 async await를 사용해야 한다.
그런데 만약 useEffect의 두번째인자에 []를 주지 않는다면, 무한루프에 빠져 계속 렌더링 한다.
맨 처음 setResult에 data를 담으면 state가 바껴서 페이지를 다시 렌더링 하고, 그러면 axios가 다시 호출되고, 그러면 다시 setResult를 건든다. 그래서 두번째인자에 []를 줘서 처음렌더링됐을때 한번만 data를 불러오게 한다.
그리고 useEffect의 반환값은 함수가 되어야 하는데, async를 바로 써버리면 promise 객체가 반환값이 되어 잘못된 코드가 되어버린다. 그래서 속에다가 async함수를 따로 작성하고 그 안에서 axios를 사용한다.
대표적인 커스텀 훅 : https://usehooks.com/useWindowSize/