A custom hook is simply a normal JS function whose purpose is to wrap all the state/logic which is closely-related and repetitive to use it wherever you want avoiding that boilerplate code and encouraging reusability.
In this project, i used a custom hook to group up a fetch feature that manages 3 related states and some logic
//states
const [userPlaces, setUserPlaces] = useState([]);
const [isFetching, setIsFetching] = useState(false);
const [error, setError] = useState();
//logic
useEffect(() => {
async function fetchPlaces() {
setIsFetching(true);
try {
const places = await fetchUserPlaces();
setUserPlaces(places);
} catch (error) {
setError({ message: error.message || 'Failed to fetch user places.' });
}
setIsFetching(false);
}
fetchPlaces();
}, []);I organized the project by adding a hooks folder and a useFetch.js file.
folderβ‘οΈ can also be namedcustomhooksor anything descriptive.fileβ‘οΈ name must start withuseto comply and take advantage ofReact Hooks Rules
export function useFetch(fetchFn,initialValue){
//modified states
const [isFetching, setIsFetching] = useState(false);
const [data, setData] = useState(initialValue);
const [error, setError] = useState();
//modified logic
useEffect(() => {
async function fetchData() {
setIsFetching(true);
try {
const data = await fetchFn();
setData(places);
} catch (error) {
setError({ message: error.message || 'Failed to fetch data.' });
}
fetchData(false);
}
fetchPlaces();
}, [fetchFn]);
return {
isFetching,
data,
setData,
error
}- Generalize
state/logicβ‘οΈ state and logic variables names are now more generic. - Flexible
Parametersβ‘οΈ addedfetchFnandinitialValueto allow dynamic usage. - Effect Dependencies β‘οΈ ensured
fetchFnis listed as a dependency for proper reusability. - Reusability β‘οΈ returns an
objectcontaining allvaluesandfunctionsto expose.
Now, this custom hook can be used as follows:
import { useFetch } from "./hooks/useFetch.js";
const { isFetching, data: userPlaces, error } = useFetch(fetchUserPlaces, []);
...
<DummyComponent isLoading={isFetching} places={userPlaces} />
{error && <Error title="An error occurred!" message={error.message} />}In AvailablePlaces.jsx, I needed to fetch available places and sort them by user location using the navigator API:
useEffect(() => {
...
navigator.geolocation.getCurrentPosition((position) => {
const sortedPlaces = sortPlacesByDistance(
places,
position.coords.latitude,
position.coords.longitude
);
...
}To integrate this with the custom hook, a customized fetch function was needed:
//create a function with all the nested behavior what is needed
async function fetchSortedPlaces(){
const places = await fetchAvailablePlaces() // first retrieve all that places
//then returns a Promise with the resolve (sortedPlaces) or reject (not handled in this case)
return new Promise((resolve,reject) => {
navigator.geolocation.getCurrentPosition((position) => {
const sortedPlaces = sortPlacesByDistance(
places,
position.coords.latitude,
position.coords.longitude
);
resolve(sortedPlaces)
})
})
}Now, use the custom hook with the fetch function
const { isFetching, data: availablePlaces, error } = useFetch(fetchSortedPlaces, []);- Create a reusable
custom hookfile (useFn.js) to manage some closely-relatedstate/logic. - Generalize the
state/logicand also addparametersfor flexibility. - Handle other use cases with
custom functions(async/Promise).
Finally, the project is cleaner and the custom hook can be easily reused across components as it is unique to each component's use.
πΈ This project is a practice exercise I learned from the Academind's React Course πΈ