Skip to content

Commit

Permalink
Merge pull request #52 from Enterwell/feature/hook-usePromise
Browse files Browse the repository at this point in the history
feat: Added usePromise hook
  • Loading branch information
AleksandarDev authored Sep 15, 2023
2 parents a35100e + ee4962e commit f99380e
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 0 deletions.
86 changes: 86 additions & 0 deletions apps/docs/pages/react-hooks/hooks/use-promise.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
---
title: usePromise
---

import { usePromise } from '@enterwell/react-hooks';
import { ComponentDescription, ComponentParameters, ComponentSource } from '../../../components/docs/ComponentDocs';

# usePromise

## Description

<ComponentDescription name="usePromise" />

### Parameters

<ComponentParameters name="usePromise" />

## Examples

```ts filename="example.ts - Example with function that returns promise" {8}
import { usePromise } from '@enterwell/react-hooks';

const getData = async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/todos');
return await response.json();
};

const { item, isLoading, error } = usePromise(getData);
```

```ts filename="example.ts - Example with function that accepts argument" {10-11}
import { useCallback } from 'react';
import { usePromise } from '@enterwell/react-hooks';

const getData = async (id: number | undefined) => {
const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`);
return await response.json();
};

const [id, setId] = useState<number | undefined>(undefined);
const getDataCallback = useCallback(() => getData(id), [id]);
const { item, isLoading, error } = usePromise(getDataCallback);
```

```ts filename="example.ts - Example with function that accepts argument, disabled (in loading state) until 'id' is set" {10-11}
import { useMemo } from 'react';
import { usePromise } from '@enterwell/react-hooks';

const getData = async (id: number) => {
const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`);
return await response.json();
};

const [id, setId] = useState<number | undefined>(undefined);
const getDataCallback = useMemo(() => id ? () => getData(id) : undefined, [id]);
const { item, isLoading, error } = usePromise(getDataCallback);
```

```ts filename="example.ts - Example with promise object" {14}
import { usePromise } from '@enterwell/react-hooks';

const getData = () => {
return new Promise(async (resolve, reject) => {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/todos');
resolve(await response.json());
} catch(err) {
reject(err);
}
});
};

const getDataPromise = getData();
const { item, isLoading, error } = usePromise(getDataPromise);
```

## Inspect

<details>
<summary>Source code</summary>
<ComponentSource
package="@enterwell/react-hooks"
directory="hooks"
name="usePromise"
/>
</details>
Empty file.
84 changes: 84 additions & 0 deletions packages/react-hooks/hooks/usePromise.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import {
useEffect,
useState,
useTransition
} from 'react';

/**
* The result of a usePromise hook.
* @public
*/
export type UsePromiseResult<T> = {
item?: T | undefined;
isLoading: boolean;
error?: string | undefined;
};

/**
* A function that returns a promise or undefined.
* @public
*/
export type PromiseFunction<T> = (Promise<T> | undefined) | (() => Promise<T> | undefined);

/**
* The hook is used to load data and handle loading and error states.
*
* @param promise - The function that returns promise or promise object to await.<br/>
* - If `promise` argument is not provided ([falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy)), the load is paused in loading state until `promise` is provided.<br/>
* - If the function returns a promise or promise object is passed, the promise will be awaited.<br/>
* - If the function returns undefined, the load is considered complete and empty.
* @returns An object with the current state of the operation.
* @public
*/
export function usePromise<T>(promise?: PromiseFunction<T>): UsePromiseResult<T> {
const [state, setState] = useState<UsePromiseResult<T>>({ isLoading: true, item: undefined, error: undefined });
const [, startTransition] = useTransition();

useEffect(() => {
let canceled = false;

// Ignore if promise not provided
if (!promise) {
return;
}

setState((curr) => ({ ...curr, isLoading: true }));

// Resolve as promise object or function
const current = typeof promise === 'function' ? promise() : promise;

// If promise is undefined, load is considered complete and empty
if (!current) {
startTransition(() => {
setState({ isLoading: false });
});
return;
}

current
.then(item => {
if (canceled) {
return;
}
startTransition(() => {
setState({ isLoading: false, item });
});
}).catch(err => {
if (canceled) {
return;
}
startTransition(() => {
setState({
isLoading: false,
error: Boolean(err) && 'toString' in err ? err.toString() : undefined
});
});
});

return () => {
canceled = true;
};
}, [promise]);

return state;
}
1 change: 1 addition & 0 deletions packages/react-hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// hook exports
export * from "./hooks/usePromise";
export * from "./hooks/useDebouncedEffect";
export * from "./hooks/useDebounce";
export * from "./hooks/useIsomorphicLayoutEffect";
Expand Down
13 changes: 13 additions & 0 deletions packages/react-hooks/temp/react-hooks.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
import * as react from 'react';
import { useEffect } from 'react';

// @public
export type PromiseFunction<T> = (Promise<T> | undefined) | (() => Promise<T> | undefined);

// @public
export function useDebounce<T>(value: T, delay: number): T;

Expand All @@ -16,6 +19,16 @@ export function useDebouncedEffect(effect: Function, deps: unknown[], delay: num
// @public
export const useIsomorphicLayoutEffect: typeof useEffect;

// @public
export function usePromise<T>(promise?: PromiseFunction<T>): UsePromiseResult<T>;

// @public
export type UsePromiseResult<T> = {
item?: T | undefined;
isLoading: boolean;
error?: string | undefined;
};

// @public
export function useResizeObserver(callback: (element: any, entry: ResizeObserverEntry) => void): react.MutableRefObject<null>;

Expand Down

0 comments on commit f99380e

Please sign in to comment.