https://github.com/ecomfe/react-suspense-boundary
A boundary component working with suspense and error
https://github.com/ecomfe/react-suspense-boundary
Last synced: 9 months ago
JSON representation
A boundary component working with suspense and error
- Host: GitHub
- URL: https://github.com/ecomfe/react-suspense-boundary
- Owner: ecomfe
- License: mit
- Created: 2019-07-19T08:17:44.000Z (almost 7 years ago)
- Default Branch: master
- Last Pushed: 2024-03-22T12:34:36.000Z (over 2 years ago)
- Last Synced: 2025-09-03T03:03:58.485Z (10 months ago)
- Language: TypeScript
- Size: 7.91 MB
- Stars: 84
- Watchers: 3
- Forks: 9
- Open Issues: 9
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# react-suspense-boundary
A boundary component working with suspense and error
Version 2.x is implemented on [use-sync-external-store](https://www.npmjs.com/package/use-sync-external-store) to align with future official suspense data fetching.
## Install
```shell
npm install react-suspense-boundary
```
## Demo
See online demo here: [https://ecomfe.github.io/react-suspense-boundary/](https://ecomfe.github.io/react-suspense-boundary/)
You can start demo app yourself by executing:
```shell
npm start
```
## Usage
### Basic
```jsx
import {Boundary, CacheProvider, useResource} from 'react-suspense-boundary';
// Create or import an async function
const fetchInfo = ({id}) => fetch(`/info/${id}`).then(response => response.json());
// Implement your presentational component
function Info({id}) {
// Call `useResource` to fetch, note the return value is an array
const [info] = useResource(fetchInfo, {id});
// There is no `loading` branch, push returned object immediately to render
return (
{info.id}: {info.name}
);
};
// Data is stored inside `CacheProvider`, suspending state is controlled with `Boundary`
export default function App() => (
);
```
### CacheProvider
`CacheProvider` is by its name a cache context where we store all resources loaded by its children.
The simpliest way to use `CacheProvider` is to provider an application level top cache:
```tsx
import {render} from 'react-dom';
import {CacheProvider} from 'react-suspense-boundary';
import {App} from './components/App';
render(
,
document.getElementById('root')
);
```
For some more complex applications, you may want to restrict data caching in a smaller scope, e.g. route level, and expire cached responses on unmount, you can put `CacheProvider` anywhere you want to make a shared cache.
### Boundary
`Boundary` components defines a boundary in your view, within a boundary all async resource fetchings and errors are collected to form a loading or error indicator.
Usually we would have mulitple `Boundary` inside a `CacheProvider`, that is, users see different sections loading individually, but all resources are shared.
A `Boundary` component receives props below:
```typescript
interface RenderErrorOptions {
recover: () => void;
}
interface BoundaryProps {
// When any of async progress is pending, boundary will render this element
pendingFallback: ReactNode;
// When any error are received, will render this function
renderError(error: Error, options: RenderErrorOptions): ReactNode;
// When any error are catched, will call this function
onErrorCaught(error: Error, info: ErrorInfo): void;
}
```
### useResource
The `useResource` hook is used to inspect an async function within a boundary:
```ts
type Resource = [
T,
{
expire(): void;
refresh(): void;
}
];
function useResource(action: (input: I) => Promise, params: I): Resource;
function useConstantResource(action: () => Promise): Resource;
```
Unlike other async hooks, `useResource` returns the result "immediately", there is no `pending` or `loading` state, no exception will throw.
Other than the result itself, the second object of `useResource`'s returned array is a a bunch of functions to manually control the cache:
- `expire` will immediately remove the cached result, causing the upper `Boundary` to be pending until `action` is resolved the next time.
- `refresh` is a function to run `action` again without removing previously cached result.
### Default configuration
`BoundaryConfigProvider` provides default configurations to `pendingFallback`, `renderError` and `onErrorCaught` props.
```javascript
import {Spin} from 'antd';
import {BoundaryConfigProvider} from 'react-suspense-boundary';
const defaultPendingFallback = ;
const defaultRenderError = error => (
{error.message}
);
const App = () => {
{/* All Boundary elements inside it receives default configurations */}
}
```
### Preload
Preload is much like resource fetching, they can be "immediately" fired within a render function:
```ts
function usePreloadResource(action: (input: I) => Promise, params: I): void;
function usePreloadConstantResource(action: () => Promise): void;
```
Preload fires resource fetching process but not abort current render.
You can also get a `preload` function using `usePreloadCallback` hook to preload any resources in effect or event handlers:
```tsx
const preload = usePreloadCallback();
preload(fetchList, {pageIndex: currentPageIndex + 1})}>
Next Page
```
## Create Your Own Cache
`react-suspense-boundary`'s built-in `CacheProvider` references a single context type, that is, you are unable to access multiple caches in a single component:
```tsx
```
By default, there is no way to a make `MyResource` to load one resource into the outer `CacheProvider` and another into the inner `CacheProvider`.
To solve this issue, we provide a `create()` function to create a custom set of providers and hooks, with a different context type so that you can use them simultaneously:
```tsx
import {CacheProvider, create, useConstantResource} from 'react-suspense-boundary';
const {
CacheProvider: GlobalCacheProvider,
useConstantResource: useGlobalConstantResource,
} = create();
function MyResource() {
// Put current user resource into global cache
const [currentUser] = useGlobalConstantResource(fetchCurrentUser);
// And other resources into local one
const [dataSource] = useConstantResource(fetchList);
return (
// ...
);
}
```
`create` function also accepts an option object to customize context's display name:
```ts
interface CreateOptions {
cacheContextDisplayName?: string;
configContextDisplayName?: string;
}
```