You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React useEffect异步setState引发额外重渲染与重复请求问题

Fixing Duplicate API Requests in useConfigDetails Hook

Let's break down why you're seeing 9 requests instead of 3, and how to fix this properly:

Root Cause Analysis

The most likely issue here is unaborted pending requests when resourceName changes rapidly. When resourceName updates, your useEffect runs again immediately, spawning a new set of 3 requests—while the previous 3 are still in flight. If this happens 3 times (e.g., resourceName changes 3 times in quick succession), you end up with 9 total requests (3 environments × 3 rounds of calls).

Another edge case to consider: if your component re-renders for unrelated reasons and accidentally triggers the useEffect again, but since your dependency is resourceName (a stable string), this shouldn't happen unless resourceName is being recreated on every render (unlikely in your current code). The abort controller fix below will cover both scenarios.

Step-by-Step Solution

Here's the optimized version of your hook with fixes to prevent duplicate requests and unnecessary re-renders:

import React, { useState, useEffect } from 'react';
import axios, { AxiosError } from 'axios';

// Assuming these types are defined elsewhere
type Environment = 'dev' | 'stage' | 'prod';
type ResourceDetails = { /* Add your type definition here */ };

export const useConfigDetails = (resourceName: string) => {
  const [isLoading, setLoading] = useState<boolean>(true);
  const [error, setError] = useState<AxiosError | undefined>(undefined);
  const [allEnvDetails, setAllEnvDetails] = useState<ResourceDetails[] | undefined>(undefined);

  useEffect(() => {
    // Create an AbortController to cancel pending requests if the effect cleans up
    const abortController = new AbortController();
    const signal = abortController.signal;

    const fetchConfigs = async () => {
      setLoading(true);
      setError(undefined); // Reset error state for new requests
      try {
        const environments: Environment[] = ['dev', 'stage', 'prod'];
        // Attach abort signal to each request to cancel if needed
        const configPromises = environments.map((env) => 
          axios.get(`someUrlGetEndpoint?resource=${resourceName}&env=${env}`, { signal })
        );
        const resp = await Promise.all(configPromises);
        setAllEnvDetails(resp);
      } catch (err) {
        // Ignore errors caused by intentional abortion
        if (!(err instanceof AxiosError) || err.code !== 'ERR_CANCELED') {
          setError(err as AxiosError);
        }
      } finally {
        // Only update loading state if the request wasn't aborted
        if (!signal.aborted) {
          setLoading(false);
        }
      }
    };

    fetchConfigs();

    // Cleanup: abort pending requests when effect re-runs or component unmounts
    return () => abortController.abort();
  }, [resourceName]); // Only re-run when resourceName changes

  return { allEnvDetails, isLoading, error };
};

Key Improvements Explained

  • AbortController Integration: This ensures that if resourceName changes before previous requests complete, those pending calls are canceled immediately. No more old requests finishing and updating state (or adding to your request count).

  • State Reset on New Requests: We explicitly set loading to true and clear the error state when starting a new fetch, keeping the UI consistent and avoiding stale error messages.

  • Async/Await for Readability: Switched from .then()/.catch() to async/await to make the code easier to follow and maintain.

  • Guard Clauses for Aborted Requests: We skip state updates if the request was aborted, preventing unnecessary re-renders caused by stale state changes.

Extra Tips to Reduce Unnecessary Re-Renders

  1. Memoize the Return Value: If your parent component re-renders too often, wrap the hook's returned object in useMemo to stabilize its reference:

    return React.useMemo(() => ({ allEnvDetails, isLoading, error }), [allEnvDetails, isLoading, error]);
    

    This ensures the returned object only changes when one of its properties updates, preventing parent components from re-rendering unnecessarily.

  2. Verify resourceName Stability: Ensure resourceName isn't being recreated on every render (e.g., if derived from an object, use useMemo to stabilize it). Since it's a string in your case, this is probably not an issue, but it's worth double-checking.

With these changes, you should only see 3 requests per resourceName change—no more duplicate calls!

内容的提问来源于stack exchange,提问作者Christie Chen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 15:07:35