Hone logo
Hone
Problems

Crafting a Reusable useAsyncFn Hook in React

The useAsyncFn hook is a powerful tool for managing asynchronous operations within React components, providing a clean and centralized way to handle loading states, errors, and data. This challenge asks you to implement this hook, enabling developers to easily execute asynchronous functions and manage their lifecycle within their components. A well-implemented useAsyncFn hook simplifies asynchronous logic, improves code readability, and reduces boilerplate.

Problem Description

You are tasked with creating a custom React hook called useAsyncFn. This hook should accept an asynchronous function as an argument and provide a set of utilities for managing its execution, loading state, and potential errors.

What needs to be achieved:

The useAsyncFn hook should return an object containing the following properties:

  • execute: A function that, when called, executes the provided asynchronous function.
  • loading: A boolean indicating whether the asynchronous function is currently executing.
  • data: The result of the asynchronous function, if it has completed successfully. Initially undefined.
  • error: An error object if the asynchronous function throws an error. Initially undefined.
  • reset: A function that resets the hook's state to its initial state (loading: false, data: undefined, error: undefined).

Key Requirements:

  • The hook should manage the loading state while the asynchronous function is executing.
  • The hook should store the result of the asynchronous function in the data property upon successful completion.
  • The hook should store any errors thrown by the asynchronous function in the error property.
  • The execute function should accept arguments that are passed directly to the asynchronous function.
  • The hook should handle multiple executions of the asynchronous function without interfering with each other.
  • The reset function should clear any pending data or errors.

Expected Behavior:

  1. Initially, loading should be false, data should be undefined, and error should be undefined.
  2. When execute is called, loading should become true.
  3. If the asynchronous function completes successfully, loading should become false, data should be set to the result of the function, and error should be undefined.
  4. If the asynchronous function throws an error, loading should become false, data should be undefined, and error should be set to the error object.
  5. Calling execute again before the previous execution completes should cancel the previous execution and start a new one.
  6. Calling reset should set loading to false, data to undefined, and error to undefined.

Edge Cases to Consider:

  • Asynchronous function throws an error.
  • Asynchronous function resolves successfully.
  • Calling execute multiple times in quick succession.
  • The asynchronous function takes a long time to complete.
  • The asynchronous function returns null or undefined.
  • The asynchronous function is never called.

Examples

Example 1:

Input:  async () => { await new Promise(resolve => setTimeout(resolve, 500)); return "Data fetched!"; }
Output: { execute: ƒ, loading: false, data: undefined, error: undefined, reset: ƒ }
Explanation: The hook is initialized with the asynchronous function.  `loading` is initially false, and other states are undefined.

Example 2:

Input: async (arg: number) => { await new Promise(resolve => setTimeout(resolve, 200)); return arg * 2; }
Output: After calling execute(5): { execute: ƒ, loading: false, data: 10, error: undefined, reset: ƒ }
Explanation:  `execute(5)` is called. After 200ms, `loading` becomes false, `data` becomes 10, and `error` remains undefined.

Example 3:

Input: async () => { await new Promise((resolve, reject) => setTimeout(() => reject(new Error("Failed to fetch data")), 300)); }
Output: After calling execute(): { execute: ƒ, loading: false, data: undefined, error: Error: Failed to fetch data, reset: ƒ }
Explanation: The asynchronous function rejects after 300ms. `loading` becomes false, `data` remains undefined, and `error` becomes an Error object.

Constraints

  • The hook must be written in TypeScript.
  • The asynchronous function passed to the hook can accept any number of arguments.
  • The hook should not rely on any external libraries beyond React.
  • The hook should be performant and avoid unnecessary re-renders.
  • The execute function should be memoized to prevent unnecessary re-renders of components that use it.

Notes

  • Consider using useState and useEffect to manage the hook's state and lifecycle.
  • Think about how to handle concurrent executions of the asynchronous function. You might want to cancel any pending requests before starting a new one.
  • The reset function is crucial for clearing the state and allowing the hook to be reused.
  • Pay close attention to the order of operations and the timing of state updates.
  • Use TypeScript's type system to ensure type safety and prevent errors.
Loading editor...
typescript