← Về danh sách bài họcBài 21/25

🔌 Bài 21: API Integration - Tích Hợp API

⏱️ Thời gian đọc: 18 phút | 📚 Độ khó: Trung bình

🎯 Sau bài học này, bạn sẽ:

1. Fetch API Cơ Bản

// Custom hook pattern
function useFetch(url) {
    const [state, setState] = useState({ data: null, loading: true, error: null });

    useEffect(() => {
        const controller = new AbortController();
        setState(prev => ({ ...prev, loading: true }));

        fetch(url, { signal: controller.signal })
            .then(res => {
                if (!res.ok) throw new Error(`HTTP ${res.status}`);
                return res.json();
            })
            .then(data => setState({ data, loading: false, error: null }))
            .catch(err => {
                if (err.name !== 'AbortError')
                    setState({ data: null, loading: false, error: err.message });
            });

        return () => controller.abort();
    }, [url]);

    return state;
}

function Posts() {
    const { data: posts, loading, error } = useFetch('/api/posts');
    if (loading) return <div className="skeleton">Loading...</div>;
    if (error) return <div className="error">Error: {error}</div>;
    return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>;
}

2. TanStack Query (React Query)

npm install @tanstack/react-query
import { QueryClient, QueryClientProvider, useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

const queryClient = new QueryClient();

function App() {
    return (
        <QueryClientProvider client={queryClient}>
            <Posts />
        </QueryClientProvider>
    );
}

function Posts() {
    const queryClient = useQueryClient();

    // GET - tự động cache, refetch, retry
    const { data: posts, isLoading, error } = useQuery({
        queryKey: ['posts'],
        queryFn: () => fetch('/api/posts').then(r => r.json()),
        staleTime: 5 * 60 * 1000, // 5 phút
    });

    // POST - mutation
    const createPost = useMutation({
        mutationFn: (newPost) => fetch('/api/posts', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(newPost)
        }).then(r => r.json()),
        onSuccess: () => {
            queryClient.invalidateQueries({ queryKey: ['posts'] });
        }
    });

    if (isLoading) return <p>Loading...</p>;
    return (
        <div>
            <button onClick={() => createPost.mutate({ title: 'New' })}>
                {createPost.isPending ? 'Creating...' : 'Add Post'}
            </button>
            {posts?.map(p => <p key={p.id}>{p.title}</p>)}
        </div>
    );
}
💡 Tại sao TanStack Query?
• Auto caching, background refetching
• Retry on error, optimistic updates
• Window focus refetching, pagination support
• Giảm 90% boilerplate code so với useEffect

📝 Tóm Tắt