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 named- customhooksor anything descriptive.
- fileβ‘οΈ name must start with- useto comply and take advantage of- React 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 πΈ