1. Hook 의 규칙
- 리액트 컴포넌트 내에서만 사용해야 함
- 중첩 함수 내에 있으면 안됨
2. Custom Hook 이 필요한 이유
- 컴포넌트 내 코드 간결화
- 커스텀 훅에서는 컴포넌트 밖의 함수 임에도 훅을 사용할 수 있다.
- 작성한 함수를 다른 컴포넌트에서 재사용 가능하다.
custom-hooks.js 커스텀 훅
import { useEffect, useState } from "react";
export function useFetch(fetchFn, initialValue) {
const [isFetching, setIsFetching] = useState();
const [error, setError] = useState();
const [fetchedData, setFetchedData] = useState(initialValue);
useEffect(() => {
async function fetchData() {
setIsFetching(true);
try {
const data = await fetchFn();
setFetchedData(data);
} catch (error) {
setError({ message: error.message || "Failed to fetch data." });
}
setIsFetching(false);
}
fetchData();
}, [fetchFn]);
return {
isFetching,
error,
fetchedData,
setFetchedData,
};
}
- 함수 앞에 use를 붙임 : 리액트는 use로 시작하는 함수를 훅으로 인식함
- 커스텀 훅 내에서 사용할 훅을 import 해와서 사용
- 사용할 함수, 값을 return 해주면 됨
커스텀 훅 사용하는 컴포넌트 1
import { useRef, useState, useCallback } from "react";
import Places from "./components/Places.jsx";
import Modal from "./components/Modal.jsx";
import DeleteConfirmation from "./components/DeleteConfirmation.jsx";
import logoImg from "./assets/logo.png";
import AvailablePlaces from "./components/AvailablePlaces.jsx";
import { fetchUserPlaces, updateUserPlaces } from "./http.js";
import Error from "./components/Error.jsx";
import { useFetch } from "./hooks/useFetch.js";
function App() {
const selectedPlace = useRef();
const [errorUpdatingPlaces, setErrorUpdatingPlaces] = useState();
const [modalIsOpen, setModalIsOpen] = useState(false);
const {
isFetching,
error,
fetchedData: userPlaces,
setFetchedData: setUserPlaces,
} = useFetch(fetchUserPlaces, []);
function handleStartRemovePlace(place) {
setModalIsOpen(true);
selectedPlace.current = place;
}
function handleStopRemovePlace() {
setModalIsOpen(false);
}
async function handleSelectPlace(selectedPlace) {
// await updateUserPlaces([selectedPlace, ...userPlaces]);
setUserPlaces((prevPickedPlaces) => {
if (!prevPickedPlaces) {
prevPickedPlaces = [];
}
if (prevPickedPlaces.some((place) => place.id === selectedPlace.id)) {
return prevPickedPlaces;
}
return [selectedPlace, ...prevPickedPlaces];
});
try {
await updateUserPlaces([selectedPlace, ...userPlaces]);
} catch (error) {
setUserPlaces(userPlaces);
setErrorUpdatingPlaces({
message: error.message || "Failed to update places.",
});
}
}
const handleRemovePlace = useCallback(
async function handleRemovePlace() {
setUserPlaces((prevPickedPlaces) =>
prevPickedPlaces.filter(
(place) => place.id !== selectedPlace.current.id,
),
);
try {
await updateUserPlaces(
userPlaces.filter((place) => place.id !== selectedPlace.current.id),
);
} catch (error) {
setUserPlaces(userPlaces);
setErrorUpdatingPlaces({
message: error.message || "Failed to delete place.",
});
}
setModalIsOpen(false);
},
[userPlaces.setUserPlaces],
);
function handleError() {
setErrorUpdatingPlaces(null);
}
return (
<>
<Modal open={errorUpdatingPlaces} onClose={handleError}>
{errorUpdatingPlaces && (
<Error
title="An error occurred!"
message={errorUpdatingPlaces.message}
onConfirm={handleError}
/>
)}
</Modal>
<Modal open={modalIsOpen} onClose={handleStopRemovePlace}>
<DeleteConfirmation
onCancel={handleStopRemovePlace}
onConfirm={handleRemovePlace}
/>
</Modal>
<header>
<img src={logoImg} alt="Stylized globe" />
<h1>PlacePicker</h1>
<p>
Create your personal collection of places you would like to visit or
you have visited.
</p>
</header>
<main>
{error && <Error title="An error occurred!" message={error.message} />}
{!error && (
<Places
title="I'd like to visit ..."
fallbackText="Select the places you would like to visit below."
isLoading={isFetching}
loadingText="Fetching your places..."
places={userPlaces}
onSelectPlace={handleStartRemovePlace}
/>
)}
<AvailablePlaces onSelectPlace={handleSelectPlace} />
</main>
</>
);
}
export default App;
커스텀 훅 사용하는 컴포넌트 2
import Places from "./Places.jsx";
import Error from "./Error.jsx";
import { sortPlacesByDistance } from "../loc.js";
import { fetchAvailablePlaces } from "../http.js";
import { useFetch } from "../hooks/useFetch.js";
async function fethchSortedPlaces() {
const places = await fetchAvailablePlaces();
return new Promise((resolve) => {
navigator.geolocation.getCurrentPosition((position) => {
const sortedPlaces = sortPlacesByDistance(
places,
position.coords.latitude,
position.coords.longitude,
);
resolve(sortedPlaces);
});
});
}
export default function AvailablePlaces({ onSelectPlace }) {
const {
isFetching,
error,
fetchedData: availablePlaces,
} = useFetch(fethchSortedPlaces, []);
if (error) {
return <Error title="An error occurred!" message={error.message} />;
}
return (
<Places
title="Available Places"
places={availablePlaces}
isLoading={isFetching}
loadingText="Fetching place data..."
fallbackText="No places available."
onSelectPlace={onSelectPlace}
/>
);
}
'React' 카테고리의 다른 글
[React] Redux (0) | 2024.04.19 |
---|---|
[React] Form Validation check (0) | 2024.04.18 |
[React] Http 연결, fetch (0) | 2024.04.16 |
[React] memo, useMemo (0) | 2024.04.11 |
[React] useContext (0) | 2024.04.08 |