EN/CH Mode
BRUCE_FE JS Interview Notes - Implement Custom useFetch Hook
Learn how to implement custom useFetch Hook in React, master data fetching, loading state management, error handling, and caching mechanisms to enhance frontend application user experience.
EN/CH Mode
Lazy to read articles? Then watch videos!
Custom useFetch Hook Overview
In React applications, data fetching is a common requirement. Custom useFetch Hook can encapsulate data fetching logic, including loading states, error handling, and even implement advanced features like caching and request deduplication, thereby simplifying component code and improving reusability.
Why Do We Need Custom useFetch Hook?
- 1. Separation of concerns: Separate data fetching logic from view logic
- 2. Code reuse: Avoid writing similar data fetching logic repeatedly in multiple components
- 3. Consistent handling: Uniformly handle loading states, errors, and responses
- 4. Feature extension: Easily add features like caching, debouncing, throttling, etc.
- 5. Simplified testing: Make components and data fetching logic easier to test independently
Basic useFetch Hook Implementation
Below is a basic useFetch Hook implementation containing data fetching, loading state, and error handling:
import { useState, useEffect } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// 重置狀態
setLoading(true);
setData(null);
setError(null);
// 獲取數據
fetch(url)
.then(response => {
// 檢查響應狀態
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(json => {
setData(json);
setLoading(false);
})
.catch(err => {
setError(err.message);
setLoading(false);
});
}, [url]); // 當 URL 變化時重新獲取數據
return { data, loading, error };
}
// 使用示例
function UserProfile({ userId }) {
const { data, loading, error } = useFetch(`/api/users/${userId}`);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
if (!data) return null;
return (
<div>
<h1>{data.name}</h1>
<p>Email: {data.email}</p>
</div>
);
}
This basic implementation provides three key states: data, loading state, and error information, allowing components to render different UIs based on these states.
useFetch Implementation with Request Cancellation
In practical applications, when components unmount or dependencies change, we need to cancel ongoing requests to avoid memory leaks and state update errors. Below is a useFetch Hook implementation using AbortController for request cancellation:
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// Create AbortController instance
const controller = new AbortController();
const signal = controller.signal;
// Reset states
setLoading(true);
setData(null);
setError(null);
// Fetch data
fetch(url, { signal })
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(json => {
setData(json);
setLoading(false);
})
.catch(err => {
// Ignore errors caused by request cancellation
if (err.name === 'AbortError') {
console.log('Fetch aborted');
} else {
setError(err.message);
setLoading(false);
}
});
// Cleanup function: Cancel request when component unmounts or dependencies change
return () => {
controller.abort();
};
}, [url]);
return { data, loading, error };
}
This improved version uses the AbortController API to cancel ongoing requests. When the component unmounts or the URL changes, the cleanup function automatically executes, canceling incomplete requests, effectively preventing memory leaks and issues with setting state on unmounted components.