// api.js
export async function getRevies() {
const response = await fetch(url);
const body = await response.json();
return body;
}
// components/App.js
import { getRevies } from "../api";
function App() {
const [items, setItems] = useState([]);
const handleLoadClick = async () => {
const { reviews } = await getRevies();
setItems(reviews);
};
return (
<div>
<button onClick={handleLoadClick}>불러오기</button>
</div>
);
}
React hook
useEffect(() => {
// side effect code
return () => {
// cleanup function
}
}, dependency list);
Side Effect
dependency list
cleanup function
Side Effect
에서 발생한 리소스를 정리하거나 해제하는 역할을 합니다.import { useEffect } from "react";
import { getRevies } from "../api";
function App() {
const handleLoad = async () => {
const { reviews } = await getRevies();
setItems(reviews);
};
useEffect(() => {
handleLoad();
}, []);
}
// api.js
export async function getRevies(order = "createdAt") {
const query = `order=${order}`;
const response = await fetch(
`url${query}`
);
const body = await response.json();
return body;
}
// components/App.js
import { useEffect } from "react";
import { getRevies } from "../api";
function App() {
const [order, setOrder] = useState("createdAt");
const handleNewestClick = () => setOrder("createdAt");
const handleBestClick = () => setOrder("rating");
const handleLoad = async (orderQuery) => {
const { reviews } = await getRevies(orderQuery);
setItems(reviews);
};
useEffect(() => {
handleLoad(order);
}, [order]);
}
// api.js
export async function getReviews({
order = "createdAt",
offset = 0,
limit = 6,
}) {
const query = `order=${order}&offset=${offset}&limit=${limit}`;
const response = await fetch(
`https://learn.codeit.kr/api/film-reviews?${query}`
);
const body = await response.json();
return body;
}
// components/App.js
import { useEffect, useState } from "react";
import ReviewList from "./ReviewList";
import { getReviews } from "../api";
const LIMIT = 6;
function App() {
const [order, setOrder] = useState("createdAt");
const [offset, setOffset] = useState(0);
const [hasNext, setHasNext] = useState(false);
const [items, setItems] = useState([]);
const sortedItems = items.sort((a, b) => b[order] - a[order]);
const handleNewestClick = () => setOrder("createdAt");
const handleBestClick = () => setOrder("rating");
const handleDelete = (id) => {
const nextItems = items.filter((item) => item.id !== id);
setItems(nextItems);
};
const handleLoad = async (options) => {
const { paging, reviews } = await getReviews(options);
if (options.offset === 0) {
setItems(reviews);
} else {
setItems([...items, ...reviews]);
}
setOffset(options.offset + options.limit);
setHasNext(paging.hasNext);
};
const handleLoadMore = async () => {
await handleLoad({ order, offset, limit: LIMIT });
};
useEffect(() => {
handleLoad({ order, offset: 0, limit: LIMIT });
}, [order]);
return (
<div>
<div>
<button onClick={handleNewestClick}>최신순</button>
<button onClick={handleBestClick}>베스트순</button>
</div>
<ReviewList items={sortedItems} onDelete={handleDelete} />
<button disabled={!hasNext} onClick={handleLoadMore}>
더 보기
</button>
</div>
);
}
export default App;
&&
, ||
논리 연산자, 삼항 연산자(조건 연산자)를 활용하여 조건부로 리액트 엘리먼트를 표시할 수 있습니다.{hasNext && <button onClick={handleLoadMore}>더 보기</button>}
function App() {
const nullValue = null;
const undefinedValue = undefined;
const trueValue = true;
const falseValue = false;
const emptyString = '';
const emptyArray = [];
return (
<div>
<p>{nullValue}</p>
<p>{undefinedValue}</p>
<p>{trueValue}</p>
<p>{falseValue}</p>
<p>{emptyString}</p>
<p>{emptyArray}</p>
</div>
);
}
export default App;
Race condition 문제를 해결하기 위해서는, 상태(state)를 변경하는 비동기 작업들이 순차적으로 실행되도록 보장해야 합니다.
비동기적 상태 변경 작업이 순서대로 처리되도록 보장하는 방법은 다음과 같습니다.
이전 상태 값을 이용하여 상태를 변경하는 방식
setState()
함수에 콜백 함수를 전달하여 직전 상태 값을 이용하여 새로운 상태를 계산할 수 있습니다.// count 상태를 1씩 증가시키는 예시
setCount((prevCount) => prevCount + 1);
useReducer() hook 사용
const [state, dispatch] = useReducer(reducer, initialState);
const fetchData = async () => {
dispatch({ type: 'start_fetching' });
const data1 = await fetchAPI1();
dispatch({ type: 'set_data1', payload: data1 });
const data2 = await fetchAPI2();
dispatch({ type: 'set_data2', payload: data2 });
dispatch({ type: 'finish_fetching' });
};
```
async/await 문법 사용
async function fetchData() {
const data1 = await fetchAPI1();
const data2 = await fetchAPI2();
// ...
}
const [state, setState] = useState(initialState);
usestate
함수에 값을 전달하면 초깃값으로 지정할 수 있습니다.const [state, setState] = useState(() => {
// 초기값을 계산
return initialState;
});
const [state, setState] = useState(0);
const handleAddClick = () => {
setState(state + 1);
}
const [state, setState] = useState({ count: 0 });
const handleAddClick = () => {
setState({ ...state, count: state.count + 1 }); // 새로운 객체 생성
}
const [count, setCount] = useState(0);
const handleAddClick = async () => {
await addCount();
setCount((prevCount) => prevCount + 1);
}
isLoading state를 만들고 try catch문을 활용해 데이터를 불러오는 동안 isLoading의 state를 true, 끝났을 때 false 값으로 설정해주면 loading 중일 때의 상태를 처리할 수 있습니다.
const [isLoading, setIsLoading] = useState(false);
let result;
try {
setIsLoading(true);
result = await getReviews(options);
} catch (error) {
console.error(error);
return;
} finally {
setIsLoading(false);
}
loadingError state를 만들고 catch문 안에서 loadingError의 state를 바꾸고, 리액트 엘리먼트로 optional chaining을 활용히여 에러 메세지를 출력하여 처리할 수 있습니다.
const [loadingError, setLoadingError] = useState(null);
const handleLoad = async (options) => {
let result;
try {
setIsLoading(true);
setLoadingError(null);
result = await getReviews(options);
} catch (error) {
setLoadingError(error);
return;
} finally {
setIsLoading(false);
}
...
}
return (
<div>
...
{loadingError?.message && <span>{loadingError.message}</span>}
</div>
);
Reference