React Async

repository·next·Indexed 24 days ago

https://github.com/async-library/react-async

A library for declarative promise resolution and data fetching in React and React Native. It provides components and hooks—including useAsync and useFetch—to manage asynchronous states such as loading, error, and success. Compatible with fetch, Axios, and GraphQL, it supports features like AbortController for request cancellation, optimistic updates, server-side rendering, and experimental React Suspense integration.

Tokens
15.8K
Snippets
30
Records
75
Agent score
79%

What's inside react-async

  1. What is React Async

    next

    React Async is a utility belt for declarative promise resolution and data fetching. It provides a React component and several hooks to handle asynchronous UI states (like loading, error, and success) without making assumptions about your data shape or the type of request being made.

    Key characteristics:

    • Agnostic: Works with fetch, Axios, GraphQL, or any native Promise-based logic.
    • Component-Level: Encourages resolving data close to where it is used, rather than at a global application level (like Redux).
    • Decoupled: It is not tied to routing, making it suitable for applications with dynamic routing or no routing at all.
    • Parallelism: Supports loading data on-demand and in parallel at the component level.
  2. Overview of React Async

    next

    React Async is a library providing React components and hooks for declarative promise resolution and data fetching. It allows you to handle every state of an asynchronous process (loading, error, success) without making assumptions about your data shape or request type. It is compatible with fetch, Axios, GraphQL, and works in both React and React Native environments.

    Key features include:

    • Multiple patterns: Choose between Render Props, Context-based helper components, or the useAsync and useFetch hooks.
    • Metadata: Provides convenient properties like isPending, startedAt, and finishedAt.
    • Actions: Provides cancel and reload actions.
    • Lifecycle hooks: Supports onResolve, onReject, and onCancel callbacks.
    • Advanced capabilities: Supports abortable fetch (via AbortController), optimistic updates (via setData), and server-side rendering (via initialValue).
    • Automation: Automatic re-run using watch or watchFn props.
    • Experimental Support: Includes experimental Suspense support.
  3. Handle asynchronous actions with `deferFn`

    next

    While promiseFn is used for automatic data fetching on render, deferFn is used for asynchronous actions that must be triggered manually (e.g., submitting a form).

    To use deferFn:

    1. Define a function that returns a Promise. This function receives three arguments: args (an array of arguments passed to run), props, and signal (an AbortSignal).
    2. Pass this function to the deferFn option in useAsync.
    3. Call the run function returned by useAsync to trigger the action. You can pass any number of arguments to run, which will appear in the args array inside your deferFn.

    This pattern is ideal for POST requests, deletions, or any action that should not happen automatically when a component mounts.

    import React, { useState } from "react"
    import { useAsync } from "react-async"
    
    // The deferFn receives (args, props, { signal })
    const subscribe = ([email], props, { signal }) =>
      fetch("/newsletter", { method: "POST", body: JSON.stringify({ email }), signal })
    
    const NewsletterForm = () => {
      // deferFn is NOT automatically invoked on render
      const { isPending, error, run } = useAsync({ deferFn: subscribe })
      const [email, setEmail] = useState("")
    
      const handleSubmit = event => {
        event.preventDefault()
        // Triggering the action manually with run()
        run(email)
      }
    
      return (
        <form onSubmit={handleSubmit}>
          <input type="email" value={email} onChange={event => setEmail(event.target.value)} />
          <button type="submit" disabled={isPending}>
            Subscribe
          </button>
          {error && <p>{error.message}</p>}
        </form>
      )
    }
  4. How React Async relates to React Suspense

    next

    React Async is conceptually similar to React Suspense but is a separate utility for managing asynchronous business logic. While Suspense is a React feature for suspending rendering during data loading, React Async provides the declarative syntax to manage those states.

    React Async currently includes experimental support for Suspense. You can enable it by passing the suspense option to the component or hook.

  5. Use helper components for cleaner rendering

    next

    React Async provides helper components to improve the readability of render functions by handling conditional rendering based on the async state.

    Using with useAsync hook

    When using the useAsync hook, you must pass the returned state object to the helper components.

    Using as compounds to <Async>

    When used inside an <Async> component, these helpers act as compound components. They automatically access the state via React Context, so you do not need to pass the state prop manually.

    Available Helpers:

    • IfPending / Async.Pending: Renders when the operation is in progress.
    • IfFulfilled / Async.Fulfilled: Renders when the operation succeeds (provides data to children).
    • IfRejected / Async.Rejected: Renders when the operation fails (provides error to children).
    // Pattern 1: With useAsync hook
    import { useAsync, IfPending, IfFulfilled, IfRejected } from "react-async"
    
    const MyComponent = () => {
      const state = useAsync({ promiseFn: loadPlayer, playerId: 1 })
      return (
        <>
          <IfPending state={state}>Loading...</IfPending>
          <IfRejected state={state}>{error => `Something went wrong: ${error.message}`}</IfRejected>
          <IfFulfilled state={state}>
            {data => (
              <div>
                <strong>Player data:</strong>
                <pre>{JSON.stringify(data, null, 2)}</pre>
              </div>
            )}
          </IfFulfilled>
        </>
      )
    }
    
    // Pattern 2: As compounds to <Async>
    import Async from "react-async"
    
    const MyComponent = () => (
      <Async promiseFn={loadPlayer} playerId={1}>
        <Async.Pending>Loading...</Async.Pending>
        <Async.Fulfilled>
          {data => (
            <div>
              <strong>Player data:</strong>
              <pre>{JSON.stringify(data, null, 2)}</pre>
            </div>
          )}
        </Async.Fulfilled>
        <Async.Rejected>{error => `Something went wrong: ${error.message}`}</Async.Rejected>
      </Async>
    )
  6. Understand the different React Async interfaces

    next

    React Async provides three primary ways to interact with the library depending on your React version and use case:

    1. <Async> Component: The classic interface using render props. Best for older React versions (v16.3+) or when you prefer a component-based approach.
    2. useAsync Hook: The standard hook interface. Functionally equivalent to the <Async> component, ideal for modern functional components.
    3. useFetch Hook: A specialized hook for the native fetch API. Use this when your primary goal is performing network requests.

    All these interfaces return a state object and accept options for configuration.

  7. What are async components in React Async

    next

    React Async follows a component-first mental model. Instead of fetching data at a high level in your application tree and passing it down via props, you perform data loading directly at the component level. These are called async components.

    An async component can serve two purposes:

    1. UI-driven: It renders its state (loading, error, or data) directly in the UI.
    2. Logic-only: It does not render any UI itself, but instead passes its state down to its children, allowing for a clean separation of concerns.
  8. Upgrade `deferFn` argument structure in v4

    next

    In v4, the deferFn signature changed to improve TypeScript interoperability.

    • Previously: Arguments passed to run were spread at the front of the arguments list.
    • Now: deferFn receives an args array as its first argument.

    You can maintain existing variable usage by using array destructuring on the first argument.

  9. Use React Suspense with React Async

    next
    You can use React's Suspense component to handle loading states when using React Async. Instead of manually checking isLoading flags, you can wrap your component tree in <Suspense fallback={<YourFallbackUI />}>. When a component using a React Async hook (like useAsync) triggers a fetch, React will automatically render the fallback UI until the promise resolves.
  10. Separate view and logic using the render props pattern

    next

    To keep components clean, you can separate logic from presentation by creating 'logic-only' components. These components use the useAsync hook to manage async state but do not render UI themselves. Instead, they use the [render props] pattern to pass the async state (including isPending, data, and error) down to a child function that handles the actual rendering.

    This approach is useful for creating reusable logic wrappers that can be used across different parts of your application with different UI implementations.

    import React from "react"
    import { useAsync } from "react-async"
    
    const fetchPerson = async ({ id }, { signal }) => {
      const response = await fetch(`https://swapi.co/api/people/${id}/`, { signal })
      if (!response.ok) throw new Error(response.statusText)
      return response.json()
    }
    
    const Person = ({ id }) => {
      const state = useAsync({ promiseFn: fetchPerson, id })
      return children(state)
    }
    
    const App = () => {
      return (
        <Person id={1}>
          {({ isPending, data, error }) => {
            if (isPending) return "Loading..."
            if (error) return <ErrorMessage {...error} />
            if (data) return <Greeting {...data} />
            return null
          }}
        </Person>
      )
    }