【问题标题】:Best Practice for handling consecutive identical useFetch calls with React Hooks?使用 React Hooks 处理连续相同的 useFetch 调用的最佳实践?
【发布时间】:2020-01-26 01:59:02
【问题描述】:

这是我构建的useFetch 代码,它很大程度上基于关于该主题的几篇知名文章:

const dataFetchReducer = (state: any, action: any) => {
  let data, status, url;
  if (action.payload && action.payload.config) {
    ({ data, status } = action.payload);
    ({ url } = action.payload.config);
  }  

  switch (action.type) {
    case 'FETCH_INIT':
      return { 
        ...state, 
        isLoading: true, 
        isError: false 
      };
    case 'FETCH_SUCCESS':
      return {
        ...state,
        isLoading: false,
        isError: false,
        data: data,
        status: status,
        url: url
      };
    case 'FETCH_FAILURE':
      return {
        ...state,
        isLoading: false,
        isError: true,
        data: null,
        status: status,
        url: url
      };
    default:
      throw new Error();
  }
}


/**
 * GET data from endpoints using AWS Access Token
 * @param {string} initialUrl   The full path of the endpoint to query
 * @param {JSON}   initialData  Used to initially populate 'data'
 */
export const useFetch = (initialUrl: ?string, initialData: any) => {
  const [url, setUrl] = useState<?string>(initialUrl);
  const { appStore } = useContext(AppContext);
  console.log('useFetch: url = ', url);
  const [state, dispatch] = useReducer(dataFetchReducer, {
    isLoading: false,
    isError: false,
    data: initialData,
    status: null,
    url: url
  });

  useEffect(() => {
    console.log('Starting useEffect in requests.useFetch', Date.now());
    let didCancel = false;
    const options = appStore.awsConfig;

    const fetchData = async () => {
      dispatch({ type: 'FETCH_INIT' });

      try {
        let response = {};
        if (url && options) {
          response = await axios.get(url, options);
        }

        if (!didCancel) {
          dispatch({ type: 'FETCH_SUCCESS', payload: response });
        }
      } catch (error) {
        // We won't force an error if there's no URL
        if (!didCancel && url !== null) {
          dispatch({ type: 'FETCH_FAILURE', payload: error.response });
        }
      }
    };

    fetchData();

    return () => {
      didCancel = true;
    };
  }, [url, appStore.awsConfig]);

  return [state, setUrl];
}

这似乎工作正常,除了一个用例:

想象一个新的客户名称或用户名或电子邮件地址被输入 - 必须检查一些数据以查看它是否已经存在以确保这些内容保持唯一性。

因此,例如,假设用户输入“我的现有公司”作为公司名称,并且该公司已经存在。他们输入数据并按Submit。此按钮的 Click 事件将被连接,以便调用对 API 端点的异步请求 - 如下所示:companyFetch('acct_mgmt/companies/name/My%20Existing%20Company')

然后组件中将有一个useEffect 构造,它将等待响应从端点返回。这样的代码可能如下所示:

  useEffect(() => {
    if (!companyName.isLoading && acctMgmtContext.companyName.length > 0) {
      if (fleetName.status === 200) {  
        const errorMessage = 'This company name already exists in the system.';
        updateValidationErrors(name, {type: 'fetch', message: errorMessage});
      } else {
        clearValidationError(name);
        changeWizardIndex('+1');
      }
    }
  }, [companyName.isLoading, companyName.isError, companyName.data]);

在上面的代码中,如果公司名称存在,则会显示错误。如果它尚不存在,则此组件所在的向导将前进。这里的关键点是处理响应的所有逻辑都包含在useEffect 中。

除非用户连续两次输入相同的公司名称,否则一切正常。在这种特殊情况下,companyFetchuseFetch 实例中的 url 依赖项不会更改,因此不会向 API 端点发送新请求。

我可以想出几种方法来尝试解决这个问题,但它们看起来都像是 hack。我在想其他人一定遇到过这个问题,很好奇他们是如何解决的。

【问题讨论】:

    标签: reactjs asynchronous react-hooks use-effect


    【解决方案1】:

    不是您问题的具体答案,更多的是另一种方法:您始终可以提供一个函数来通过自定义挂钩触发重新获取,而不是依靠 useEffect 来捕获所有不同的情况。

    如果您想这样做,请在您的 useFetch 中使用 useCallback,这样您就不会创建无限循环:

    const triggerFetch = useCallback(async () => {
      console.log('Starting useCallback in requests.useFetch', Date.now());
      const options = appStore.awsConfig;
    
      const fetchData = async () => {
        dispatch({ type: 'FETCH_INIT' });
    
        try {
          let response = {};
          if (url && options) {
            response = await axios.get(url, options);
          }
    
            dispatch({ type: 'FETCH_SUCCESS', payload: response });
        } catch (error) {
          // We won't force an error if there's no URL
          if (url !== null) {
            dispatch({ type: 'FETCH_FAILURE', payload: error.response });
          }
        }
      };
    
      fetchData();
    
    }, [url, appStore.awsConfig]);
    

    ..在钩子的末尾:

     return [state, setUrl, triggerFetch];
    

    您现在可以在消费组件中的任何位置使用triggerRefetch() 以编程方式重新获取数据,而不是检查useEffect 中的每个案例。

    这是一个完整的例子:

    CodeSandbox: useFetch with trigger

    【讨论】:

      【解决方案2】:

      对我来说,这与“如何强制我的浏览器跳过特定资源的缓存”有点相关——我知道,XHR 没有被缓存,只是类似的情况。在那里,我们可以通过在 URL 中提供一些随机的无意义参数来避免缓存。所以你也可以这样做。

      const [requestIndex, incRequest] = useState(0);
      ...
      const [data, updateURl] = useFetch(`${url}&random=${requestIndex}`);
      const onSearchClick = useCallback(() => {
        incRequest();
      }, []);
      

      【讨论】:

      • 我发现 Paul Cowan 的这篇文章以及随后的 cmets 非常有启发性:blog.logrocket.com/frustrations-with-react-hooks 如果您在 9 月 17 日向下滚动到 Ciprian@3:15pm 的评论,您'我会看到一种类似于我上面的方法,但是一种有效的方法。也就是说,Karen Grigoryan 的第一条评论是最有趣的。在其中,他解释说直接从事件处理程序调用异步函数在 React 中不是一个好方法。我已经通过设置一个本身触发 useEffect 的状态变量来测试这种方法。这似乎是最好的!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-01-21
      • 2011-08-13
      • 1970-01-01
      • 1970-01-01
      • 2019-04-25
      • 1970-01-01
      • 2015-12-02
      相关资源
      最近更新 更多