Skip to content

Commit 8ba9b2e

Browse files
committed
Initial commit
0 parents  commit 8ba9b2e

File tree

8 files changed

+6288
-0
lines changed

8 files changed

+6288
-0
lines changed

.editorconfig

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
2+
root = true
3+
4+
[*]
5+
charset = utf-8
6+
end_of_line = lf
7+
indent_size = 2
8+
indent_style = space
9+
trim_trailing_whitespace = true
10+
insert_final_newline = true
11+
12+
[*.md]
13+
trim_trailing_whitespace = false
14+
15+
[*.{json,yml}]
16+
indent_size = 2
17+
indent_style = space
18+
19+
[Makefile]
20+
indent_style = tab

.eslintrc.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
module.exports = {
2+
root: true,
3+
parser: 'babel-eslint',
4+
parserOptions: {
5+
allowImportExportEverywhere: true,
6+
codeFrame: false,
7+
},
8+
extends: [
9+
'plugin:eslint-comments/recommended',
10+
'plugin:promise/recommended',
11+
'airbnb',
12+
],
13+
plugins: [
14+
'promise',
15+
'html',
16+
'react-hooks',
17+
],
18+
settings: {
19+
'import/resolver': {
20+
webpack: {
21+
config: './webpack.config.js',
22+
},
23+
},
24+
},
25+
rules: {
26+
'import/prefer-default-export': 'off', // prefer named export
27+
'space-before-function-paren': ['error', {
28+
anonymous: 'always',
29+
named: 'always', // use space
30+
asyncArrow: 'always',
31+
}],
32+
'react/jsx-filename-extension': ['error', {
33+
extensions: ['.js'], // no .jsx
34+
}],
35+
'eslint-comments/disable-enable-pair': ['error', {
36+
allowWholeFile: true,
37+
}],
38+
'no-restricted-syntax': [
39+
'error',
40+
'ForInStatement',
41+
'LabeledStatement',
42+
'WithStatement',
43+
"BinaryExpression[operator='in']",
44+
],
45+
'react-hooks/rules-of-hooks': 'error',
46+
'spaced-comment': ['error', 'always', { markers: [':', '::'] }],
47+
'react/require-default-props': 'off', // optional props without defaults
48+
'react/sort-comp': 'off', // do not sort React class fields/methods
49+
'object-curly-newline': ['warn', {
50+
ObjectExpression: { minProperties: 5, multiline: true, consistent: true },
51+
ObjectPattern: { minProperties: 5, multiline: true, consistent: true },
52+
ImportDeclaration: { minProperties: 5, multiline: true, consistent: true },
53+
ExportDeclaration: { minProperties: 5, multiline: true, consistent: true },
54+
}],
55+
'quote-props': ['warn', 'as-needed', { 'numbers': true }],
56+
'max-len': ['warn', {
57+
code: 100,
58+
tabWidth: 2,
59+
ignoreComments: true,
60+
ignoreUrls: true,
61+
ignoreStrings: true,
62+
ignoreTemplateLiterals: true,
63+
ignoreRegExpLiterals: true
64+
}],
65+
'no-nested-ternary': 'warn',
66+
'arrow-body-style': 'warn'
67+
},
68+
};

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules/
2+
\.idea
3+
\.vscode

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2019 doasync
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# React effect manager
2+
3+
[![NPM Version][npm-image]][npm-url] ![NPM Downloads][downloads-image] [![GitHub issues][issues-image]][issues-url] [![Telegram][telegram-image]][telegram-url]
4+
5+
[npm-image]: https://img.shields.io/npm/v/cached-effect.svg
6+
[npm-url]: https://www.npmjs.com/package/cached-effect
7+
[downloads-image]: https://img.shields.io/npm/dw/cached-effect.svg
8+
[issues-image]: https://img.shields.io/github/issues/doasync/cached-effect.svg
9+
[issues-url]: https://github.com/doasync/cached-effect/issues
10+
[telegram-image]: http://i.imgur.com/WANXk3d.png
11+
[telegram-url]: https://t.me/doasync
12+
13+
Use cached effects in React
14+
15+
## Installation
16+
17+
```bash
18+
npm install cached-effect
19+
```
20+
21+
or
22+
23+
```bash
24+
yarn add cached-effect
25+
```
26+
27+
## Usage
28+
29+
```js
30+
import { createEffect } from './cached-effect'
31+
```
32+
33+
#### `createEffect` function
34+
35+
Wrap an async function (that returns promise) in `createEffect`:
36+
37+
```js
38+
export const fetchUsers = createEffect(({ organizationId }) => http
39+
.get(`/organizations/${organizationId}/users`)
40+
.then(getData)
41+
.then(getUsers))
42+
```
43+
44+
#### `useCache` hook
45+
46+
Then wrap your effect in `useCache` hook in your React component:
47+
48+
```js
49+
const [users, usersError] = useCache(fetchUsers)
50+
```
51+
52+
`useCache` returns an array of `[result, error]` which is
53+
equal to `[undefined, null]` by default.
54+
55+
#### Running effects
56+
57+
In order to populate the cache you need to run your effect in your component:
58+
59+
```js
60+
useEffect(() => {
61+
fetchUsers({ organizationId }) // or fetchUsers.once (see below)
62+
}, [])
63+
```
64+
65+
In the above example, users will be fetched after the render is committed
66+
to the screen.
67+
68+
You can also fetch users (trigger your effect) manually just calling it:
69+
70+
```js
71+
<Button
72+
type='button'
73+
onClick={() => fetchUsers({ organizationId })}
74+
/>
75+
```
76+
77+
#### `.once()` method
78+
79+
You can run your effect only once, for example,
80+
if you have many components using this effect:
81+
82+
```js
83+
useEffect(() => {
84+
fetchUsers.once({ organizationId })
85+
}, [])
86+
```
87+
88+
89+
#### `usePending` hook
90+
91+
If you need to show a spinner for example, you can use `usePending` hook
92+
to get current pending status (true/false):
93+
94+
```js
95+
const usersLoading = usePending(fetchUsers)
96+
```
97+
98+
#### `useError` hook
99+
100+
If you need to show only an error somewhere for this effect, use `useError` hook
101+
to get current error (or `null` if your effect is done successfully):
102+
103+
```js
104+
const usersError = useError(fetchUsers)
105+
```
106+
107+
### Tip
108+
109+
If you found this hook useful, please star this package on [GitHub](https://github.com/doasync/cached-effect)
110+
111+
### Author
112+
@doasync

index.js

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/* eslint-disable no-use-before-define, promise/catch-or-return */
2+
3+
const { useState } = require('react');
4+
5+
const CACHE = {
6+
result: 0,
7+
error: 1,
8+
};
9+
10+
const promiseMap = new WeakMap();
11+
12+
function useCache (effect, kind) {
13+
const [state, setState] = useState([undefined, null]);
14+
const thunk = effect.use.getCurrent();
15+
const fpromise = promiseMap.get(thunk);
16+
17+
if (fpromise) {
18+
if (fpromise.cache) {
19+
return kind !== undefined
20+
? fpromise.cache()[kind]
21+
: fpromise.cache();
22+
}
23+
fpromise
24+
.then(result => setState([result, null]))
25+
.catch(error => setState([undefined, error]));
26+
} else {
27+
const effectCreate = effect.create;
28+
// eslint-disable-next-line no-param-reassign
29+
effect.create = payload => effectCreate(payload)
30+
.then(result => setState([result, null]))
31+
.catch(error => setState([undefined, error]));
32+
}
33+
34+
return kind !== undefined ? state[kind] : state;
35+
}
36+
37+
function useError (effect) {
38+
return useCache(effect, CACHE.error);
39+
}
40+
41+
function usePending (effect) {
42+
const thunk = effect.use.getCurrent();
43+
const fpromise = promiseMap.get(thunk);
44+
45+
if (!thunk || !fpromise) {
46+
return false;
47+
}
48+
49+
return !fpromise.cache;
50+
}
51+
52+
function createEffect (handler) {
53+
const instance = payload => instance.create(payload);
54+
55+
instance.once = payload => promiseMap.get(thunk) || instance.create(payload);
56+
57+
instance.use = (fn) => {
58+
instance.use.called = false;
59+
thunk = fn;
60+
return instance;
61+
};
62+
63+
instance.use.getCurrent = () => thunk;
64+
instance.use.called = false;
65+
66+
instance.create = (payload) => {
67+
if (thunk === undefined) {
68+
throw new Error('no thunk used in effect');
69+
}
70+
const fpromise = exec(payload, thunk);
71+
instance.use.called = true;
72+
promiseMap.set(thunk, fpromise);
73+
return fpromise;
74+
};
75+
76+
let thunk = handler;
77+
78+
return instance;
79+
}
80+
81+
function exec (args, thunk) {
82+
let promise;
83+
let errorSync;
84+
let done = false;
85+
let fpromise;
86+
87+
try {
88+
promise = thunk(args);
89+
done = true;
90+
} catch (err) {
91+
errorSync = err;
92+
}
93+
94+
if (done === false) {
95+
fpromise = Promise.reject(errorSync);
96+
fpromise.cache = () => [undefined, errorSync];
97+
98+
return fpromise;
99+
}
100+
101+
if (
102+
typeof promise === 'object'
103+
&& promise !== null
104+
&& typeof promise.then === 'function'
105+
) {
106+
fpromise = promise.then(
107+
(result) => {
108+
fpromise.cache = () => [result, null];
109+
return result;
110+
},
111+
(error) => {
112+
fpromise.cache = () => [undefined, error];
113+
throw error;
114+
},
115+
);
116+
117+
return fpromise;
118+
}
119+
120+
fpromise = Promise.resolve(promise);
121+
fpromise.cache = () => [promise, null];
122+
123+
return fpromise;
124+
}
125+
126+
module.exports = {
127+
CACHE,
128+
useCache,
129+
useError,
130+
usePending,
131+
createEffect,
132+
};

0 commit comments

Comments
 (0)