【问题标题】:How can I pass a param to a promise inside usePromise React hook如何将参数传递给 usePromise React 钩子中的 Promise
【发布时间】:2020-02-24 01:25:27
【问题描述】:

我创建了一个usePromise React Hook,它应该能够解析各种 javascript 承诺并返回每个结果和状态:数据、解析状态和错误。 我能够让它在没有任何参数的情况下传递函数,但是当我尝试将其更改为允许参数时,我得到一个无限循环。

const usePromise = (promise: any): [any, boolean, any] => {
  const [data, setData] = useState<object | null>(null);
  const [error, setError] = useState<object | null>(null);
  const [fetching, setFetchingState] = useState<boolean>(true);

  useEffect(() => {
    setFetchingState(true);
    promise
      .then((data: object) => {
        setData(data);
      })
      .catch((error: object) => {
        setError(error);
      })
      .finally(() => {
        setFetchingState(false);
      });
  }, [promise]);

  return [data, fetching, error];
};

const apiCall = (param?: string): Promise<any> => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve({ response: `Response generated with your param ${param}.` });
    }, 500);
  });
};

const App = (): React.Element => {
  // How can I pass an argument to apiCall?
  const [response, fetching, error] = usePromise(apiCall(5));
  console.log("render"); // This logs infinitely
  return <div>{JSON.stringify({ response, fetching, error })}</div>;
};

您可以在https://codesandbox.io/s/react-typescript-fl13w

处查看工作代码(不带参数)

还有 bug(标签卡住了,请注意):https://codesandbox.io/s/react-typescript-9ow82

注意:我想在不使用来自 NPM 或类似的 usePromise 单一函数库的情况下找到解决方案

【问题讨论】:

  • 您的代码是否处理、promises.all 类型的响应?如果没有,则删除 [promise] 作为依赖项并在 usePromise 中进行类似 () => apiCall(5) 的调用

标签: javascript reactjs promise react-hooks react-16


【解决方案1】:

自定义挂钩可能会执行多次。你应该这样设计,你想做的所有事情(例如 API 调用)都在 useEffect 钩子中。这可以通过一个回调来实现,然后在挂钩中调用该回调。

此外,类型安全性稍高:

 const usePromise = <T>(task: () => Promise<T>) => {
   const [state, setState] = useState<[T?, boolean, Error?]>([null, true, null]);


   useEffect(() => {
       task()
         .then(result => setState([result, false, null])
         .catch(error => setState([null, false, error]);      
  }, []); // << omit the condition here, functions don't equal each other²

  return state;
};

// Then used as 
usePromise(() => apiCall(5));

² 是的,这通常是一种不好的做法,但由于 task 不应该在这里更改,我认为这很好


根据要求,这是我在一些项目中使用的版本:

 export function useAPI<Q, R>(api: (query: Q) => Promise<R | never>) {
  const [state, setState] = useState<{ loading?: true, pending?: true, error?: string, errorCode?: number, result?: R }>({ pending: true });

  async function run(query: Q) {
    if(state.loading) return;
    setState({ loading: true });

    try {
        const result = await api(query);
        setState({ result });
    } catch(error) {
        if(error instanceof HTTPError) {
            console.error(`API Error: ${error.path}`, error);
            setState({ error: error.message, errorCode: error.code });
        } else {
            setState({ error: error.message, errorCode: NaN });
        }
    }
  }

  function reset() {
     setState({ pending: true });
  }

  return [state, run, reset] as const;

}

【讨论】:

  • 原因是 useEffect 具有依赖关系 promise 这是一个包含 setTimeOut/ promise 的函数,并且每次组件呈现时,都会将新的 setTimeout 从 API 推送到 callbackQueue section ,从而无限异步并导致重新渲染
  • 嘿@Jonas 非常感谢您的回答。我发现,如果组件已安装并且apiCall 的参数发生更改,usePromise 结果不会更新。我认为它应该接收函数的参数作为钩子比较器(useEffect 的第二个参数)。你怎么看? codesandbox.io/s/react-typescript-bgqrp
  • 这很难。当前正在进行的 API 调用会发生什么?确保正确取消,并注意参数。对象在 react 中通过引用进行比较。
  • 是的,这很好!如果您有任何usePromise 或比我更好的通用解决方案,请分享。非常感谢!
  • @rashomon 是的,我确实有一个我经常使用的,不确定它是否完全适合您的用例。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-20
  • 2021-06-29
  • 2022-06-15
  • 1970-01-01
  • 2020-08-15
  • 1970-01-01
相关资源
最近更新 更多