# \<AsyncBoundary />

Handles loading and error conditions of Suspense.

In React 18, this will create a [concurrent split](https://react.dev/reference/react/useTransition), and in 16 and 17 it will show loading fallbacks. If there is an irrecoverable error, it will show an error fallback.

> **Tip**
>
> Learn more about boundary placement by learning how to [co-locate data dependencies](https://dataclient.io/docs/getting-started/data-dependency.md)

## Usage

Place `AsyncBoundary` [at or above navigational boundaries](https://dataclient.io/docs/getting-started/data-dependency.md#boundaries) like **pages, routes, or modals**.

**React Router**

```tsx {9,11} title="Dashboard.tsx"
import { AsyncBoundary } from '@data-client/react';
import { Outlet } from 'react-router';

export default function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>
      <section>
        <AsyncBoundary>
          <Outlet />
        </AsyncBoundary>
      </section>
    </div>
  );
}
```

**NextJS**

```tsx {12} title="app/dashboard/layout.tsx"
import { AsyncBoundary } from '@data-client/react';

export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div>
      <h1>Dashboard</h1>
      <section>
        <AsyncBoundary>{children}</AsyncBoundary>
      </section>
    </div>
  );
}
```

**Expo**

```tsx {18,20} title="app/dashboard/_layout.tsx"
import { AsyncBoundary } from '@data-client/react';
import { Slot } from 'expo-router';
import { Image, StyleSheet } from 'react-native';

import ParallaxScrollView from '@/components/ParallaxScrollView';

export default function DashboardLayout() {
  return (
    <ParallaxScrollView
      headerBackgroundColor={{ light: '#A1CEDC', dark: '#1D3D47' }}
      headerImage={
        <Image
          source={require('@/assets/images/my-logo.png')}
          style={styles.logo}
        />
      }
    >
      <AsyncBoundary>
        <Slot />
      </AsyncBoundary>
    </ParallaxScrollView>
  );
}

const styles = StyleSheet.create({
  logo: { height: 178, width: 290 },
});
```

**Antd Modal**

```tsx title="ModalOpen.tsx"
import { AsyncBoundary } from '@data-client/react';
import { useState } from 'react';
import { Button, Modal } from 'antd';

import MyModalBody from './MyModalBody';

export default function ModalOpen() {
  const [isModalOpen, setIsModalOpen] = useState(false);
  const showModal = () => setIsModalOpen(true);
  const handleOk = () => setIsModalOpen(false);
  const handleCancel = () => setIsModalOpen(false);
  return (
    <>
      <Button type="primary" onClick={showModal}>
        Open Modal
      </Button>
      <Modal title="Basic Modal" open={isModalOpen} onOk={handleOk} onCancel={handleCancel}>
        <AsyncBoundary>
          <MyModalBody />
        </AsyncBoundary>
      </Modal>
    </>
  );
}
```

Then [useSuspense()](https://dataclient.io/docs/api/useSuspense.md) in the components that render the data. Any errors or loading state
from _any_ descendant of the `<AsyncBoundary />` will be rendered at the `<AsyncBoundary />`. This consolidation
of fallback UI improves performance and usability.

```ts
function SuspendingComponent() {
  const data = useSuspense(getMyThing);

  return <div>{data.text}</div>;
}
```

## Props

```ts
interface BoundaryProps {
  children: React.ReactNode;
  fallback?: React.ReactNode;
  errorClassName?: string;
  errorComponent?: React.ComponentType<{
    error: NetworkError;
    resetErrorBoundary: () => void;
    className?: string;
  }>;
  listen?: (resetListener: () => void) => () => void;
}
```

### fallback

Any renderable (React Node) element to show when loading

### errorComponent

Component to handle caught errors

#### Custom fallback example {#custom-fallback}

```tsx
import React from 'react';
import { DataProvider, AsyncBoundary } from '@data-client/react';
import Router from './Router';

function ErrorPage({
  error,
  className,
  resetErrorBoundary,
}: {
  error: Error;
  resetErrorBoundary: () => void;
  className?: string;
}) {
  return (
    <pre role="alert" className={className}>
      {error.message} <button onClick={resetErrorBoundary}>Reset</button>
    </pre>
  );
}

export default function App() {
  return (
    <DataProvider>
      <AsyncBoundary fallback="loading" errorComponent={ErrorPage}>
        <Router />
      </AsyncBoundary>
    </DataProvider>
  );
}
```

### errorClassName

`className` to forward to [errorComponent](#errorcomponent)

### listen

Subscription handler to reset error state on events like URL location changes. This is great
for placing a boundary to wrap routing components.

An example using [Anansi Router](https://www.npmjs.com/package/@anansi/router), which uses
[history](https://www.npmjs.com/package/history) subscription.

```tsx
import { useRouter, Link, MatchedRoute } from '@anansi/router';
import { AsyncBoundary } from '@data-client/react';

function App() {
  const { history } = useRouter();
  return (
    <div>
      <nav>
        <Link name="Home">Coin App</Link>
      </nav>
      <main>
        <AsyncBoundary listen={history.listen}>
          <MatchedRoute index={0} />
        </AsyncBoundary>
      </main>
    </div>
  );
}
```
