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 namedcustomhooks
or anything descriptive.file
β‘οΈ name must start withuse
to 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
β‘οΈ addedfetchFn
andinitialValue
to allow dynamic usage. - Effect Dependencies β‘οΈ ensured
fetchFn
is listed as a dependency for proper reusability. - Reusability β‘οΈ returns an
object
containing allvalues
andfunctions
to 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 hook
file (useFn.js
) to manage some closely-relatedstate/logic
. - Generalize the
state/logic
and also addparameters
for 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 πΈ