Frontend Development 9 min read

Avoiding Race Conditions in React Data Fetching with Hooks, Boolean Flags, and useRequest

This article explains what race conditions are, demonstrates how they can occur in React components during asynchronous data fetching, and provides practical solutions using component lifecycle checks, boolean cancellation flags, the ahooks useRequest hook, and React Suspense to ensure correct UI updates.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Avoiding Race Conditions in React Data Fetching with Hooks, Boolean Flags, and useRequest

Race Condition

Race Condition, translated as 竞态条件, describes a situation where a system or process output depends on the order or timing of uncontrolled events.

Simple example:

if (x == 5) // The "Check"
{
  y = x * 2; // The "Act"
  // If another thread changes x between the check and the act, y may not equal 10.
}

You might wonder how this can happen in JavaScript, which is single‑threaded.

React and Race Condition

Even in the front‑end, asynchronous rendering can cause race conditions. A typical data‑fetching component is shown below.

class Article extends Component {
  state = { article: null };
  componentDidMount() {
    this.fetchData(this.props.id);
  }
  async fetchData(id) {
    const article = await API.fetchArticle(id);
    this.setState({ article });
  }
  // ...
}

To handle updates when the component receives a new id , we modify the code:

class Article extends Component {
  state = { article: null };
  componentDidMount() {
    this.fetchData(this.props.id);
  }
  componentDidUpdate(prevProps) {
    if (prevProps.id !== this.props.id) {
      this.fetchData(this.props.id);
    }
  }
  async fetchData(id) {
    const article = await API.fetchArticle(id);
    this.setState({ article });
  }
  // ...
}

When the id changes, a new request is sent and the state is updated. However, if the user quickly clicks to view another profile, the earlier request may finish later and overwrite the newer data, causing incorrect UI.

One way to solve this is to cancel the previous request or use a boolean flag to decide whether to update:

function Article({ id }) {
  const [article, setArticle] = useState(null);
  useEffect(() => {
    let didCancel = false;
    async function fetchData() {
      const article = await API.fetchArticle(id);
      if (!didCancel) {
        setArticle(article);
      }
    }
    fetchData();
    return () => { didCancel = true; };
  }, [id]);
  // ...
}

Alternatively, the useRequest hook from ahooks can manage the async call and its loading state automatically:

function Article({ id }) {
  const { data, loading, error } = useRequest(() => fetchArticle(id), { refreshDeps: [id] });
  // ...
}

The article also provides a full demo with a fakeFetch function that returns a promise resolved after a random delay, illustrating the race condition problem and the fixes.

Suspense

Using React Suspense together with a wrapper that converts a promise into a resource can also prevent race conditions:

function wrapPromise(promise) {
  let status = "pending";
  let result;
  let suspender = promise.then(
    r => { status = "success"; result = r; },
    e => { status = "error"; result = e; }
  );
  return {
    read() {
      if (status === "pending") throw suspender;
      else if (status === "error") throw result;
      else if (status === "success") return result;
    }
  };
}

const fakeFetch = person => new Promise(res => setTimeout(() => res(`${person}'s data`), Math.random()*5000));

function fetchData(userId) { return wrapPromise(fakeFetch(userId)); }

const initialResource = fetchData('Nick');

function User({ resource }) {
  const data = resource.read();
  return
{data}
;
}

function App() {
  const [person, setPerson] = useState('Nick');
  const [resource, setResource] = useState(initialResource);
  const handleClick = name => () => { setPerson(name); setResource(fetchData(name)); };
  return (
    <>
Nick's Profile
Deb's Profile
Joe's Profile
{person}
);
}

The article concludes that the next post will dive deeper into Suspense.

React Series

React 之 createElement 源码解读

React 之元素与组件的区别

React 之 Refs 的使用和 forwardRef 的源码解读

React 之 Context 的变迁与背后实现

This pre‑heat series aims to explore React APIs and their implementations from a source‑code perspective, with an expected total of about 50 articles.

frontendReactHooksuseEffectrace conditionuseRequest
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.