Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add useAbortableEffect #16

Merged
merged 1 commit into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/src/pages/_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"type": "separator",
"title": "Hooks"
},
"use-abortable-effect": {},
"use-clipboard": {},
"use-composition-input": {},
"use-debounced-state": {},
Expand Down
7 changes: 3 additions & 4 deletions docs/src/pages/best-practice.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export {
useSidebarActive, useSetSidebarActive
};
```

```tsx filename="src/App.tsx" copy
import { useIntersection } from 'foxact/use-intersection';

Expand Down Expand Up @@ -124,7 +124,8 @@ const ExampleComponent = ({ dataKey }: ExampleComponentProps) => {

Here, although the request for `data1` happened before `data2`, the response for `data2` is received before `data1`. And `useIsMountedRef` doesn't help with that.

To properly avoid `setData(data1)` from being called, the correct pattern is:
To properly avoid `setData(data1)` from being called, the correct pattern is described below.
You can also use [`useAbortableEffect`](./use-abortable-effect) instead.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's append this after with the texy like this:

If your platform supports AbortController, you can also use the useAbortableEffect.


```tsx
interface ExampleComponentProps {
Expand Down Expand Up @@ -155,5 +156,3 @@ const ExampleComponent = ({ dataKey }: ExampleComponentProps) => {
│ Request data 2 ────► data2 response (setData(data2)) │
| isCancelled: false | isCancelled: false, setData(data2)
```


67 changes: 67 additions & 0 deletions docs/src/pages/use-abortable-effect.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
title: useAbortableEffect
---

# useAbortableEffect

`useEffect` that gives you an [AbortSignal](https://mdn.io/AbortSignal).

## Usage

```js
import { useAbortableEffect } from 'foxact/use-use-abortable-effect';

function Component() {
useAbortableEffect(signal => {
item.addEventListener('event', () => {
// ...
}, { signal })
}, [item]);
}
```

```js
// before
useEffect(() => {
let isCancelled = false;
someAsyncStuff().then(data => {
if (!isCancelled) {
setData(data);
}
});

return () => {
isCancelled = true;
};
}, [dataKey]);

// after
useAbortableEffect((signal) => {
someAsyncStuff().then(data => {
if (!signal.aborted) return
setData(data);
});
}, [dataKey]);
```

Note that [`eslint-plugin-react-hooks`](https://www.npmjs.com/package/eslint-plugin-react-hooks) requires extra configuration in order to check dependency array for third-party hooks:

```json filename=".eslintrc.json" copy
{
"rules": {
"react-hooks/exhaustive-deps": [
"warn",
{
"additionalHooks": "useAbortableEffect"
}
]
}
}
```

But if you do not want to configure it, `foxact/use-abortable-effect` also provides another named export `useEffect` as an alias of `useAbortableEffect`:

```diff
- import { useEffect } from 'react';
+ import { useEffect } from 'foxact/use-abortable-effect';
```
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions src/use-abortable-effect/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import 'client-only';
import { type EffectCallback, useEffect as useEffectFromReact, type DependencyList } from 'react';

export const useAbortableEffect = (callback: (signal: AbortSignal) => ReturnType<EffectCallback>, deps: DependencyList) => {
useEffectFromReact(() => {
const controller = new AbortController();
const signal = controller.signal;
const f = callback(signal);
return () => {
controller.abort();
f?.();
};
}, deps);
};
export const useEffect = useAbortableEffect;
Loading