Skip to content

Latest commit

 

History

History
127 lines (97 loc) · 3.04 KB

File metadata and controls

127 lines (97 loc) · 3.04 KB

Two fishing hooks

@openformation/use-business

Handle busy state for multiple asynchronous processes

Installation

Using npm:

npm install --save @openformation/use-business

Using yarn:

yarn add @openformation/use-business

Using pnpm:

pnpm add --save @openformation/use-business

API

useBusiness = () => Business;

interface Business {
  readonly isBusy: boolean;
  perform<R>(processFn: () => Promise<R>): R;
}

Usage

Let's say, we have a component LoadMoreList that renders a list of items and is able to fetch more items with a touch of a button and add them to the list:

const LoadMoreList = <I>(props: {
  loadItems: (page: number) => Promise<I[]>;
  renderItem: (item: I) => React.ReactNode;
}) => {
  const [items, setItems] = React.useState<I[]>([]);
  const [page, setPage] = React.useState(0);
  const business = useBusiness();
  const loadMore = React.useCallback(() => setPage((page) => page + 1), [
    setPage,
  ]);

  React.useEffect(async () => {
    const loadedItems = await props.loadItems(page);

    setItems((items) => [...items, ...loadedItems]);
}, [page, setItems]);

  return (
    <>
      <ul>
        {items.map((item, index) => (
          <li key={String(index)}>
            {renderItem(item)}
          </li>
        ))}
      </ul>
      <button onClick={loadMore}>
        Load more
      </button>
    </>
  );
};

Because loadItems may take a while, we now want to show a loading Indicator while it's running. Also, the button should be disabled while new items are being loaded to prevent weird behavior through too many clicks and parallel fetching.

This is where useBusiness comes in. It provides a function perform that can be wrapped around the call to any process that returns a Promise. It also exposes a property isBusy that'll be true as long as any Promise that has been started is still running.

The integration looks like this:

const LoadMoreList = <I>(props: {
  loadItems: (page: number) => Promise<I[]>;
  renderItem: (item: I) => React.ReactNode;
}) => {
  const [items, setItems] = React.useState<I[]>([]);
  const [page, setPage] = React.useState(0);
  const business = useBusiness();
  const loadMore = React.useCallback(() => setPage((page) => page + 1), [
    setPage,
  ]);

  React.useEffect(async () => {
    const loadedItems = await business.perform(() => props.loadItems(page));

    setItems((items) => [...items, ...loadedItems]);
  }, [page, setItems]);

  return (
    <>
      <ul>
        {items.map((item, index) => (
          <li key={String(index)}>
            {renderItem(item)}
          </li>
        ))}
        {business.isBusy ? <li key="loading">Loading...</li> : null}
      </ul>
      <button onClick={loadMore} disabled={business.isBusy}>
        Load more
      </button>
    </>
  );
};

Hint: If perform was called multiple times, isBusy stays true until all Promises are settled.

LICENSE

see LICENSE