Stop writing useEffect for data fetching. Use Request Strategies instead.

⚠️ 【Draft – Pending Review】

Stop writing useEffect for data fetching. Use Request Strategies instead.

I’ve been a React developer for about three years. And for most of that time, I wrote data fetching the same way everyone else did:

useEffect(() => {
  fetch('/api/users')
    .then(res => res.json())
    .then(setData)
    .catch(setError)
    .finally(() => setLoading(false));
}, []);

It works. But it’s not good.

Every list page. Every search box. Every filter. Same pattern. And the code kept growing — loading states, error handling, debounce timers, race condition flags. A simple user list with search would easily hit 80 lines of boilerplate.

Then I found alova, and I realized: I’d been fighting problems that didn’t need to exist.

The Before: useEffect Hell

Here’s what my code used to look like:

// 🔴 Before: 80 lines of manual state management
import { useState, useEffect, useCallback } from 'react';

function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [keyword, setKeyword] = useState('');
  const [page, setPage] = useState(1);

  const fetchUsers = useCallback(async () => {
    setLoading(true);
    setError(null);
    try {
      const res = await fetch(`/api/users?keyword=${keyword}&page=${page}&pageSize=10`);
      const data = await res.json();
      setUsers(data.list);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }, [keyword, page]);

  useEffect(() => { fetchUsers(); }, [fetchUsers]);

  // Manual debounce
  useEffect(() => {
    const t = setTimeout(() => fetchUsers(), 500);
    return () => clearTimeout(t);
  }, [keyword]);

  // Manual race condition handling
  useEffect(() => {
    let cancelled = false;
    fetch(`/api/users?keyword=${keyword}&page=${page}`)
      .then(res => res.json())
      .then(data => { if (!cancelled) setUsers(data.list); });
    return () => { cancelled = true; };
  }, [keyword, page]);

  if (loading) return <Spinner />;
  if (error) return <Error msg={error} />;

  return (
    <div>
      <SearchInput value={keyword} onChange={setKeyword} />
      <UserList data={users} />
    </div>
  );
}

This code has several issues:

  1. Too many manual states — loading, error, data all declared by hand
  2. DIY debounce — setTimeout/clearTimeout everywhere, easy to mess up
  3. Race conditions — the classic cancelled flag pattern, error-prone
  4. Dependency chains — useCallback + useEffect, changing one prop means tracing the whole chain

I wrote this pattern dozens of times. Every new project, same boilerplate.

The After: Request Strategies with alova

Then I switched to alova, and the same component became this:

// 🟢 After: 15 lines with alova request strategies
import { useRequest, useWatcher } from 'alova/client';
import { alovaInstance } from './api';

function UserList() {
  const [keyword, setKeyword] = useState('');

  // List data — auto-managed loading/data/error
  const { loading, data: users = [], error } = useRequest(
    () => alovaInstance.Get('/api/users', {
      params: { page: 1, pageSize: 10 }
    }),
    { initialData: [] }
  );

  // Search — debounce + race condition handling built in
  const { data: searchResult = [] } = useWatcher(
    () => alovaInstance.Get('/api/users', {
      params: { keyword, page: 1, pageSize: 10 }
    }),
    [keyword],
    { debounce: 500 }
  );

  if (loading) return <Spinner />;
  if (error) return <Error msg={error.message} />;

  return (
    <div>
      <SearchInput value={keyword} onChange={setKeyword} />
      <UserList data={users} />
    </div>
  );
}

This isn’t magic. It’s design.

alova transforms data fetching from imperative to declarative. Instead of telling the computer how to fetch, you tell it what to fetch — and the library handles the rest.

What you used to write by hand What alova does for you
3x useState for loading/data/error useRequest returns them automatically
useEffect + useCallback chains Declarative watchers with auto dependency management
setTimeout / clearTimeout for debounce debounce: 500 — one line
cancelled flag for race conditions Built-in abort on next request
Manual URL parameter construction params object, auto-serialized

What Are “Request Strategies”?

alova isn’t a simple fetch wrapper. It’s a strategy-based request library that covers the most common data-fetching patterns in frontend development:

Strategy Hook You tell it It handles
useRequest What to fetch loading, data, error, send, abort
useWatcher What state to watch debounce, auto-refetch, race conditions
usePagination Page config Page state, preloading, optimistic updates
useForm Form data Draft persistence, auto-submit, reset
useAutoRequest Polling config Auto-polling, focus refresh, reconnect
useCaptcha Phone number Countdown timer, auto-send
useSerialRequest Dependent calls Pass previous result to next request
useRetriableRequest Retry config Exponential backoff with jitter

Each hook maps to a pattern you’ve probably implemented dozens of times. The goal isn’t to write less code — it’s to not write it at all.

The Results

After switching to alova across my projects, here’s what I’ve seen:

  • 60% less boilerplate — pages went from ~150 lines to ~60 lines
  • Fewer bugs — no more “forgot to clearTimeout” or “wrong cancelled flag”
  • Faster onboarding — new devs learn “use this hook for this pattern” instead of debugging useEffect chains
  • Better code review — request logic is declarative and self-documenting

Getting Started

npm install alova

Then pick an adapter:

npm install @alova/fetch     # For browsers / Node.js
npm install alova/axios       # If you're on axios

And write your first strategic request:

import { createAlova } from 'alova';
import fetchAdapter from '@alova/fetch';
import ReactHook from 'alova/react';

export const alovaInstance = createAlova({
  baseURL: 'https://api.example.com',
  statesHook: ReactHook,
  requestAdapter: fetchAdapter(),
  responded: res => res.json()
});

// In your component:
function Profile() {
  const { loading, data, error } = useRequest(
    () => alovaInstance.Get('/user/profile'),
    { initialData: {} }
  );
  // ...
}

Stop writing useEffect for data fetching. Your time is better spent on actual product logic.

Give alova a try. The docs are at alova.js.org, the source is on GitHub, and the community is welcoming.

Using alova v3. Tested in React, Vue, and Svelte.

上一篇 his is a submission for the [GitHub Finish-Up-A-Thon Challenge] BY SK MOTALIB
下一篇 Truss Analysis: Solving a Frame One Joint at a Time