|  | 
|  | 1 | +import { useState, useEffect } from "react"; | 
|  | 2 | +import axios from "axios"; | 
|  | 3 | +import { useAuth } from "../context/AuthContext"; // To potentially use the JWT token | 
|  | 4 | + | 
|  | 5 | +/** | 
|  | 6 | + * Custom hook for fetching data from an API endpoint. | 
|  | 7 | + * Handles loading, data, and error states, and includes JWT authorization. | 
|  | 8 | + * * @param {string} url - The API endpoint URL to fetch from. | 
|  | 9 | + * @param {object} options - Optional Axios request configuration (e.g., method, data). | 
|  | 10 | + * @returns {{data: any, loading: boolean, error: Error | null, refetch: () => void}} | 
|  | 11 | + */ | 
|  | 12 | +const useFetch = (url, options = {}) => { | 
|  | 13 | +  const [data, setData] = useState(null); | 
|  | 14 | +  const [loading, setLoading] = useState(true); | 
|  | 15 | +  const [error, setError] = useState(null); | 
|  | 16 | + | 
|  | 17 | +  // Get the current JWT token from context (or local storage directly) | 
|  | 18 | +  // NOTE: This assumes useAuth is working and provides the token or access to it. | 
|  | 19 | +  const { token } = useAuth(); | 
|  | 20 | + | 
|  | 21 | +  const fetchApiData = async (signal) => { | 
|  | 22 | +    setLoading(true); | 
|  | 23 | +    setError(null); | 
|  | 24 | + | 
|  | 25 | +    try { | 
|  | 26 | +      const headers = { | 
|  | 27 | +        "Content-Type": "application/json", | 
|  | 28 | +        ...options.headers, | 
|  | 29 | +      }; | 
|  | 30 | + | 
|  | 31 | +      // If a token exists, add the Authorization header | 
|  | 32 | +      if (token) { | 
|  | 33 | +        headers["Authorization"] = `Bearer ${token}`; | 
|  | 34 | +      } | 
|  | 35 | + | 
|  | 36 | +      const response = await axios({ | 
|  | 37 | +        method: "GET", // Default to GET | 
|  | 38 | +        url, | 
|  | 39 | +        signal, | 
|  | 40 | +        ...options, | 
|  | 41 | +        headers: headers, | 
|  | 42 | +      }); | 
|  | 43 | + | 
|  | 44 | +      setData(response.data); | 
|  | 45 | +    } catch (err) { | 
|  | 46 | +      if (axios.isCancel(err)) { | 
|  | 47 | +        // Ignore AbortController cancellation errors | 
|  | 48 | +        return; | 
|  | 49 | +      } | 
|  | 50 | +      setError(err); | 
|  | 51 | +      console.error(`Error fetching ${url}:`, err); | 
|  | 52 | +    } finally { | 
|  | 53 | +      // Ensure loading is set to false, even on error | 
|  | 54 | +      if (!signal.aborted) { | 
|  | 55 | +        setLoading(false); | 
|  | 56 | +      } | 
|  | 57 | +    } | 
|  | 58 | +  }; | 
|  | 59 | + | 
|  | 60 | +  useEffect(() => { | 
|  | 61 | +    // AbortController prevents state updates on unmounted components | 
|  | 62 | +    const controller = new AbortController(); | 
|  | 63 | +    const signal = controller.signal; | 
|  | 64 | + | 
|  | 65 | +    fetchApiData(signal); | 
|  | 66 | + | 
|  | 67 | +    // Cleanup function to cancel the request if the component unmounts | 
|  | 68 | +    return () => { | 
|  | 69 | +      controller.abort(); | 
|  | 70 | +    }; | 
|  | 71 | +  }, [url, token]); // Re-run if URL or token changes | 
|  | 72 | + | 
|  | 73 | +  // Function to manually trigger a refetch | 
|  | 74 | +  const refetch = () => { | 
|  | 75 | +    fetchApiData(new AbortController().signal); | 
|  | 76 | +  }; | 
|  | 77 | + | 
|  | 78 | +  return { data, loading, error, refetch }; | 
|  | 79 | +}; | 
|  | 80 | + | 
|  | 81 | +export default useFetch; | 
0 commit comments