Skip to content

Commit af1ff83

Browse files
Added scopeArg option for useAsyncCallback hook;
Added `skipFirst` option for `useAsyncEffect` hook; Updated README.md; Updated dependencies;
1 parent 2eef5d7 commit af1ff83

File tree

7 files changed

+1229
-889
lines changed

7 files changed

+1229
-889
lines changed

CHANGELOG.md

+44-21
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,55 @@
1-
# Change Log
2-
All notable changes to this project will be documented in this file.
1+
### Changelog
32

4-
The format is based on [Keep a Changelog](http://keepachangelog.com/)
5-
and this project adheres to [Semantic Versioning](http://semver.org/).
3+
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
64

7-
## [0.4.0] - 2021-01-11
5+
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
86

9-
### Added
10-
- `queueSize` option for `useAsyncEffect`;
7+
#### [v0.4.0](https://github.com/DigitalBrainJS/use-async-effect/compare/v0.4.0...v0.4.0)
118

12-
### Updated
13-
- reworked `cancelPrevious` and `combine` logic;
9+
#### [v0.4.0](https://github.com/DigitalBrainJS/use-async-effect/compare/v0.3.0...v0.4.0)
1410

15-
## [0.3.0] - 2021-01-07
11+
> 11 January 2021
1612
17-
### Fixed
18-
- Bug with `useAsyncEffect` user cancellation;
13+
- Added `queueSize` option for `useAsyncEffect`; [`0436e46`](https://github.com/DigitalBrainJS/use-async-effect/commit/0436e46fc55309ccf5965221ba6389356e1b2259)
1914

20-
## [0.2.0] - 2021-01-06
15+
#### [v0.3.0](https://github.com/DigitalBrainJS/use-async-effect/compare/v0.2.1...v0.3.0)
2116

22-
### Added
23-
- `useAsyncCallback` hook
24-
- typings
17+
> 7 January 2021
2518
26-
### Updated
27-
- `useAsyncEffect` now returns a cancel function directly instead of an array with it.
19+
- Fixed bug with useAsyncEffect user cancellation; [`3ccd038`](https://github.com/DigitalBrainJS/use-async-effect/commit/3ccd03813d0b7e01132118de660f2b030967389e)
2820

29-
## [0.1.0] - 2021-01-04
21+
#### [v0.2.1](https://github.com/DigitalBrainJS/use-async-effect/compare/v0.2.0...v0.2.1)
3022

31-
### Added
32-
- initial version
23+
> 6 January 2021
24+
25+
- Fixed demo links in the README.md; [`1cb5f11`](https://github.com/DigitalBrainJS/use-async-effect/commit/1cb5f11a5dc035d2755b8638b9844d726521f481)
26+
- Added typings config to the package.json; [`481afc8`](https://github.com/DigitalBrainJS/use-async-effect/commit/481afc81612fe7c01b516cb11b9b8f084d63d3b1)
27+
28+
#### [v0.2.0](https://github.com/DigitalBrainJS/use-async-effect/compare/v0.1.2...v0.2.0)
29+
30+
> 6 January 2021
31+
32+
- Added useAsyncCallback hook; [`baca6a7`](https://github.com/DigitalBrainJS/use-async-effect/commit/baca6a73792cf47262f3a21eade60600ba8cf877)
33+
- spellcheck; [`34c70ea`](https://github.com/DigitalBrainJS/use-async-effect/commit/34c70ea037f9e592a3d1f039948bf588e68dac6c)
34+
35+
#### [v0.1.2](https://github.com/DigitalBrainJS/use-async-effect/compare/v0.1.1...v0.1.2)
36+
37+
> 5 January 2021
38+
39+
- Added console.error for catch handler; [`df9b78f`](https://github.com/DigitalBrainJS/use-async-effect/commit/df9b78fa77a0a823436d96f7a14c80bcbe972fdc)
40+
41+
#### [v0.1.1](https://github.com/DigitalBrainJS/use-async-effect/compare/v0.1.0...v0.1.1)
42+
43+
> 5 January 2021
44+
45+
- Refactored package.json; [`3a7253a`](https://github.com/DigitalBrainJS/use-async-effect/commit/3a7253a9726f4f65acc0164838f226c81ff2ca8f)
46+
- Added prepublishOnly & postversion scripts; [`40ebb4d`](https://github.com/DigitalBrainJS/use-async-effect/commit/40ebb4d0c20834121645b16bcedeb4f719092df3)
47+
- Renamed package; [`a0d6894`](https://github.com/DigitalBrainJS/use-async-effect/commit/a0d68945bc10c165a93aea0271a10a01b651c15c)
48+
49+
#### v0.1.0
50+
51+
> 5 January 2021
52+
53+
- Initial commit [`5529feb`](https://github.com/DigitalBrainJS/use-async-effect/commit/5529febb3c24fb3d6f1dccc1bd210e9f6e88bf26)
54+
- Improved README.md; [`a0a7144`](https://github.com/DigitalBrainJS/use-async-effect/commit/a0a7144ef707085e879f828d49620761227dba0b)
55+
- Refactored; [`590756c`](https://github.com/DigitalBrainJS/use-async-effect/commit/590756c6dbd7053200c8c46a49bd9bb1d76716b1)

README.md

+135-69
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,43 @@
55
[![Stars](https://badgen.net/github/stars/DigitalBrainJS/use-async-effect)](https://github.com/DigitalBrainJS/use-async-effect/stargazers)
66

77
## useAsyncEffect2 :snowflake:
8-
The library provides a React hook with ability to automatically cancel asynchronous code inside it.
9-
It just makes it easier to write cancelable asynchronous code that doesn't cause
10-
the following React issue when unmounting, if your asynchronous tasks that change state are pending:
8+
This library provides `useAsyncEffect` and `useAsyncCallback` React hooks that make possible to cancel async code
9+
inside it and unsubscribe the internal routines if needed (request, timers etc.).
10+
These hooks are very useful for data fetching, since you don't have to worry about request
11+
cancellation when components unmounts. So just forget about `isMounted` flag and `AbortController`,
12+
which were used to protect your components from React leak warning, these hooks do that for you automatically.
13+
Writing cancellable async code becomes easy.
14+
## Installation :hammer:
15+
- Install for node.js using npm/yarn:
16+
17+
```bash
18+
$ npm install use-async-effect2
19+
```
20+
21+
```bash
22+
$ yarn add use-async-effect2
23+
```
24+
25+
## Why
26+
Every asynchronous procedure in your component that changes its state must properly handle the unmount event
27+
and stop execution in some way before attempting to change the state of the unmounted component, otherwise
28+
you will get the well-known React leakage warning:
1129
````
1230
Warning: Can't perform a React state update on an unmounted component.
1331
This is an no-op, but it indicates a memory leak in your application.
1432
To fix, cancel all subscriptions and asynchronous task in "a useEffect cleanup function".
1533
````
34+
1635
It uses [c-promise2](https://www.npmjs.com/package/c-promise2) to make it work.
17-
When it used in conjunction with other libraries from CPromise ecosystem,
36+
When used in conjunction with other libraries from CPromise ecosystem,
1837
such as [cp-fetch](https://www.npmjs.com/package/cp-fetch) and [cp-axios](https://www.npmjs.com/package/cp-axios),
1938
you get a powerful tool for building asynchronous logic for your React components.
20-
You just have to use `generators` instead of an async function to make your code cancellable,
21-
but basically, that just means you will have to use `yield` instead of `await` keyword.
22-
## Installation :hammer:
23-
- Install for node.js using npm/yarn:
2439

25-
```bash
26-
$ npm install use-async-effect2 c-promise2
27-
```
40+
`useAsyncEffect` & `useAsyncCallback` hooks make cancelable async functions from generators,
41+
providing the ability to cancel async sequence in any stage in automatically way, when the related component unmounting.
42+
It's totally the same as async functions, but with cancellation- you need use 'yield' instead of 'await'. That's all.
2843

29-
```bash
30-
$ yarn add use-async-effect2 c-promise2
31-
```
32-
## Usage example
33-
Minimal example with json request [Live demo](https://codesandbox.io/s/friendly-murdock-wxq8u?file=/src/TestComponent.js)
44+
JSON fetching using `useAsyncEffect` [Live demo](https://codesandbox.io/s/friendly-murdock-wxq8u?file=/src/TestComponent.js)
3445
````jsx
3546
import React from "react";
3647
import {useState} from "react";
@@ -41,16 +52,45 @@ function JSONViewer(props) {
4152
const [text, setText] = useState("");
4253

4354
useAsyncEffect(function* () {
44-
setText("fetching...");
45-
const response = yield cpFetch(props.url);
55+
setText("fetching...");
56+
const response = yield cpFetch(props.url); // will throw a CanceledError if component get unmounted
4657
const json = yield response.json();
4758
setText(`Success: ${JSON.stringify(json)}`);
4859
}, [props.url]);
4960

5061
return <div>{text}</div>;
5162
}
5263
````
53-
Example with a timeout & error handling ([Live demo](https://codesandbox.io/s/async-effect-demo1-vho29?file=/src/TestComponent.js)):
64+
Notice: the related network request will be aborted, when unmounting.
65+
66+
It can be even easier with the [axios wrapper](https://www.npmjs.com/package/cp-axios) ([Demo](https://codesandbox.io/s/use-async-effect-axios-tiny-lsbpv?file=/src/TestComponent.js)):
67+
````javascript
68+
import React, { useState } from "react";
69+
import { useAsyncEffect } from "use-async-effect2";
70+
import cpAxios from "cp-axios";
71+
72+
export default function TestComponent(props) {
73+
const [text, setText] = useState("");
74+
75+
const cancel = useAsyncEffect(
76+
function* () {
77+
const response = yield cpAxios(props.url);
78+
setText(JSON.stringify(response.data));
79+
},
80+
[props.url]
81+
);
82+
83+
return (
84+
<div className="component">
85+
<div className="caption">useAsyncEffect demo:</div>
86+
<div>{text}</div>
87+
<button onClick={cancel}>Cancel request</button>
88+
</div>
89+
);
90+
}
91+
````
92+
93+
An example with a timeout & error handling ([Live demo](https://codesandbox.io/s/async-effect-demo1-vho29?file=/src/TestComponent.js)):
5494
````jsx
5595
import React, { useState } from "react";
5696
import { useAsyncEffect, E_REASON_UNMOUNTED } from "use-async-effect2";
@@ -59,8 +99,10 @@ import cpFetch from "cp-fetch";
5999

60100
export default function TestComponent(props) {
61101
const [text, setText] = useState("");
102+
const [isPending, setIsPending] = useState(true);
62103

63-
const cancel = useAsyncEffect(function* ({ onCancel }) {
104+
const cancel = useAsyncEffect(
105+
function* ({ onCancel }) {
64106
console.log("mount");
65107

66108
this.timeout(props.timeout);
@@ -71,9 +113,11 @@ export default function TestComponent(props) {
71113
setText("fetching...");
72114
const response = yield cpFetch(props.url);
73115
const json = yield response.json();
116+
setIsPending(false);
74117
setText(`Success: ${JSON.stringify(json)}`);
75118
} catch (err) {
76-
CanceledError.rethrow(err, E_REASON_UNMOUNTED); //passthrough
119+
CanceledError.rethrow(err, E_REASON_UNMOUNTED); //passthrough for UNMOUNTED rejection
120+
setIsPending(false);
77121
setText(`Failed: ${err}`);
78122
}
79123

@@ -88,56 +132,71 @@ export default function TestComponent(props) {
88132
<div className="component">
89133
<div className="caption">useAsyncEffect demo:</div>
90134
<div>{text}</div>
91-
<button onClick={cancel}>Abort</button>
135+
<button onClick={cancel} disabled={!isPending}>
136+
Cancel request
137+
</button>
92138
</div>
93139
);
94140
}
95141
````
96-
useAsyncCallback example ([Live demo](https://codesandbox.io/s/use-async-callback-bzpek?file=/src/TestComponent.js)):
142+
`useAsyncCallback` example: fetch with progress capturing & cancellation
143+
([Live demo](https://codesandbox.io/s/use-async-callback-axios-catch-ui-l30h5?file=/src/TestComponent.js)):
97144
````javascript
98-
import React from "react";
99-
import { useState } from "react";
100-
import { useAsyncCallback, E_REASON_UNMOUNTED } from "../../lib/use-async-effect";
145+
import React, { useState } from "react";
146+
import { useAsyncCallback, E_REASON_UNMOUNTED } from "use-async-effect2";
101147
import { CPromise, CanceledError } from "c-promise2";
148+
import cpAxios from "cp-axios";
149+
import { ProgressBar } from "react-bootstrap";
102150

103-
export default function TestComponent2() {
104-
const [text, setText] = useState("");
151+
export default function TestComponent(props) {
152+
const [text, setText] = useState("");
153+
const [progress, setProgress] = useState(0);
154+
const [isFetching, setIsFetching] = useState(false);
105155

106-
const asyncRoutine = useAsyncCallback(
107-
function* (a, b) {
108-
setText(`Stage1`);
109-
yield CPromise.delay(1000);
110-
setText(`Stage2`);
111-
yield CPromise.delay(1000);
112-
setText(`Stage3`);
113-
yield CPromise.delay(1000);
114-
setText(`Done`);
115-
return Math.random();
116-
},
117-
{ cancelPrevious: true }
118-
);
119-
120-
const onClick = () => {
121-
asyncRoutine(123, 456).then(
122-
(value) => {
123-
setText(`Result: ${value}`);
124-
},
125-
(err) => {
126-
console.warn(err);
127-
CanceledError.rethrow(E_REASON_UNMOUNTED);
128-
setText(`Fail: ${err}`);
129-
}
130-
);
131-
};
132-
133-
return (
134-
<div className="component">
135-
<div className="caption">useAsyncCallback demo:</div>
136-
<button onClick={onClick}>Run async job</button>
137-
<div>{text}</div>
138-
<button onClick={() => asyncRoutine.cancel()}>Abort</button>
139-
</div>
140-
);
156+
const fetchUrl = useAsyncCallback(
157+
function* (options) {
158+
try {
159+
setIsFetching(true);
160+
this.innerWeight(3); // for progress calculation
161+
this.progress(setProgress);
162+
setText("fetching...");
163+
const response = yield cpAxios(options).timeout(props.timeout);
164+
yield CPromise.delay(500); // just for fun
165+
yield CPromise.delay(500); // just for fun
166+
setText(JSON.stringify(response.data));
167+
setIsFetching(false);
168+
} catch (err) {
169+
CanceledError.rethrow(err, E_REASON_UNMOUNTED);
170+
setText(err.toString());
171+
setIsFetching(false);
172+
}
173+
},
174+
[props.url]
175+
);
176+
177+
return (
178+
<div className="component">
179+
<div className="caption">useAsyncEffect demo:</div>
180+
<div>{isFetching ? <ProgressBar now={progress * 100} /> : text}</div>
181+
{!isFetching ? (
182+
<button
183+
className="btn btn-success"
184+
onClick={() => fetchUrl(props.url)}
185+
disabled={isFetching}
186+
>
187+
Fetch data
188+
</button>
189+
) : (
190+
<button
191+
className="btn btn-warning"
192+
onClick={() => fetchUrl.cancel()}
193+
disabled={!isFetching}
194+
>
195+
Cancel request
196+
</button>
197+
)}
198+
</div>
199+
);
141200
}
142201
````
143202

@@ -150,25 +209,32 @@ just use the codesandbox [demo](https://codesandbox.io/s/async-effect-demo1-vho2
150209

151210
## API
152211

153-
### useAsyncEffect(generatorFn, deps?): (cancel():boolean)
212+
### useAsyncEffect(generatorFn, deps?: []): (cancel():boolean)
213+
### useAsyncEffect(generatorFn, options?: object): (cancel():boolean)
154214
A React hook based on [`useEffect`](https://reactjs.org/docs/hooks-effect.html), that resolves passed generator as asynchronous function.
155215
The asynchronous generator sequence and its promise of the result will be canceled if
156-
the effect cleanup process is started before it completes.
216+
the effect cleanup process started before it completes.
157217
The generator can return a cleanup function similar to the `useEffect` hook.
158-
- `generatorFn(...userArgs, scope: CPromise)` : `GeneratorFunction` - generator to resolve as an async function.
159-
The last argument passed to this function and `this` refer to the CPromise instance.
160-
- `deps?: any[]` - effect dependencies
218+
- `generatorFn(scope: CPromise)` : `GeneratorFunction` - generator to resolve as an async function.
219+
Generator context (`this`) refers to the CPromise instance.
220+
- `deps?: any[]` - effect dependencies
221+
- `options.deps?: any[]` - skip the first render
222+
- `options.skipFirst?: boolean` - skip first render
161223

162-
### useAsyncCallback(generatorFn, deps?): CPromiseAsyncFunction
224+
### useAsyncCallback(generatorFn, deps?: []): CPromiseAsyncFunction
163225
### useAsyncCallback(generatorFn, options?: object): CPromiseAsyncFunction
164226
This hook makes an async callback that can be automatically canceled on unmount or by user request.
227+
- `generatorFn([scope: CPromise], ...userArguments)` : `GeneratorFunction` - generator to resolve as an async function.
228+
Generator context (`this`) and the first argument (if `options.scopeArg` is set) refer to the CPromise instance.
229+
165230
#### options:
166231
- `deps: any[]` - effect dependencies
167232
- `combine:boolean` - subscribe to the result of the async function already
168233
running with the same arguments or run a new one.
169234
- `cancelPrevious:boolean` - cancel the previous pending async function before running a new one.
170235
- `concurrency: number=0` - set concurrency limit for simultaneous calls. `0` mean unlimited.
171236
- `queueSize: number=0` - set max queue size.
237+
- `scopeArg: boolean=false` - pass CPromise scope to the generator function as the first argument.
172238

173239
## Related projects
174240
- [c-promise2](https://www.npmjs.com/package/c-promise2) - promise with cancellation, decorators, timeouts, progress capturing, pause and user signals support

0 commit comments

Comments
 (0)