├── .gitignore
├── .prettierrc
├── LICENSE.md
├── README.md
├── jest.config.js
├── package.json
├── src
├── __snapshots__
│ └── usePromise.spec.ts.snap
├── usePromise.spec.ts
└── usePromise.ts
├── tsconfig.json
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | .DS_Store
3 | node_modules
4 | .rts2_cache_cjs
5 | .rts2_cache_es
6 | .rts2_cache_umd
7 | dist
8 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "always",
3 | "singleQuote": true,
4 | "semi": false
5 | }
6 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Jiri Spac.
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-promise
2 |
3 | [](https://nodei.co/npm/react-promise/)
4 |
5 | a react.js hook for general promise written in typescript.
6 | Let's consider a trivial example: you have a promise such as this
7 |
8 | ```javascript
9 | let prom = new Promise(function(resolve, reject) {
10 | setTimeout(function() {
11 | resolve('a value')
12 | }, 100)
13 | })
14 | ```
15 |
16 | and you want to make a component, which renders out in it's body 'a value'. Without react-promise, such component may look like this:
17 |
18 | ```javascript
19 | class ExampleWithoutAsync extends React.Component { // you can't use stateless component because you need a state
20 | constructor () {
21 | super()
22 | this.state = {}
23 | }
24 | componentDidMount() {
25 | prom.then((value) => {
26 | this.setState({val: value})
27 | })
28 | }
29 | render () {
30 | if (!this.state.val) return null
31 | return
{this.state.val}
32 | }
33 |
34 | // or you could use a combination of useEffect and useState hook, which is basically the implementation of this small library
35 | ```
36 |
37 | and with react-promise:
38 |
39 | ```tsx
40 | import usePromise from 'react-promise';
41 |
42 | const ExampleWithAsync = (props) => {
43 | const {value, loading} = usePromise(prom)
44 | if (loading) return null
45 | return {value}
}
46 | }
47 | ```
48 |
49 | ## API
50 |
51 | The only argument can be a promise or a promise resolving thunk:
52 |
53 | ```ts
54 | usePromise(
55 | promiseOrFn: (() => Promise) | Promise
56 | )
57 | ```
58 |
59 | it might be desirable to let the usePromise call the promise returnig function, because you often don't want to do that inside the render of your functional component.
60 |
61 | Full state object interface returned by the hook looks like:
62 |
63 | ```ts
64 | {
65 | loading: boolean
66 | error: Error | null
67 | value: T | undefined // where T is your shape of the resolved data you expect obviously
68 | }
69 | ```
70 |
71 | ## install
72 |
73 | With npm:
74 |
75 | ```
76 | npm i react-promise
77 | ```
78 |
79 | [For version 2 docs, refer to the old readme](https://github.com/capaj/react-promise/tree/1691b7202be806db5f41784d6e2cc9d231a3975c#react-promise).
80 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: 'ts-jest',
3 | testEnvironment: 'node',
4 | testRegex: '(\\.(spec))\\.(js?|ts?|jsx?|tsx?)$'
5 | }
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-promise",
3 | "version": "3.0.2",
4 | "source": "src/usePromise.ts",
5 | "main": "dist/cjs/usePromise.js",
6 | "module": "dist/esm/usePromise.js",
7 | "typings": "dist/esm/usePromise.d.ts",
8 | "files": [
9 | "dist"
10 | ],
11 | "scripts": {
12 | "build": "yarn build:esm && yarn build:cjs",
13 | "prepare": "npm run build",
14 | "build:esm": "tsc --module esnext --target esnext --outDir dist/esm",
15 | "build:cjs": "tsc --module commonjs --target es5 --outDir dist/cjs",
16 | "test": "jest",
17 | "jw": "jest --watch"
18 | },
19 | "devDependencies": {
20 | "@types/jest": "^24.0.12",
21 | "@types/react": "^16.8.15",
22 | "husky": "^2.1.0",
23 | "jest": "^24.7.1",
24 | "microbundle": "^0.11.0",
25 | "prettier": "^1.17.0",
26 | "pretty-quick": "^1.10.0",
27 | "react": "^16.8.6",
28 | "react-hooks-testing-library": "^0.5.0",
29 | "react-test-renderer": "^16.8.6",
30 | "ts-jest": "^24.0.2",
31 | "typescript": "^3.4.5"
32 | },
33 | "husky": {
34 | "hooks": {
35 | "pre-commit": "pretty-quick --staged"
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/__snapshots__/usePromise.spec.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`usePromise works for rejected promise 1`] = `
4 | Object {
5 | "error": null,
6 | "loading": true,
7 | "value": undefined,
8 | }
9 | `;
10 |
11 | exports[`usePromise works for rejected promise 2`] = `
12 | Object {
13 | "error": [Error: an error],
14 | "loading": false,
15 | "value": undefined,
16 | }
17 | `;
18 |
19 | exports[`usePromise works for resolved promise 1`] = `
20 | Object {
21 | "error": null,
22 | "loading": true,
23 | "value": undefined,
24 | }
25 | `;
26 |
27 | exports[`usePromise works for resolved promise 2`] = `
28 | Object {
29 | "error": null,
30 | "loading": false,
31 | "value": "a value",
32 | }
33 | `;
34 |
35 | exports[`usePromise works when providing a promise thunk 1`] = `
36 | Object {
37 | "error": null,
38 | "loading": true,
39 | "value": undefined,
40 | }
41 | `;
42 |
43 | exports[`usePromise works when providing a promise thunk 2`] = `
44 | Object {
45 | "error": null,
46 | "loading": false,
47 | "value": "a value from promise returning thunk",
48 | }
49 | `;
50 |
--------------------------------------------------------------------------------
/src/usePromise.spec.ts:
--------------------------------------------------------------------------------
1 | import usePromise from './usePromise'
2 | import { renderHook, act } from 'react-hooks-testing-library'
3 |
4 | describe('usePromise', () => {
5 | it('works for resolved promise', (done) => {
6 | let prom = new Promise((resolve) => {
7 | setTimeout(function() {
8 | act(() => {
9 | resolve('a value')
10 | })
11 | }, 49)
12 | })
13 |
14 | renderHook(() => {
15 | act(() => {
16 | expect(usePromise(prom)).toMatchSnapshot()
17 | })
18 |
19 | setTimeout(() => {
20 | done()
21 | }, 60)
22 | })
23 | })
24 |
25 | it('works for rejected promise', (done) => {
26 | let prom = new Promise((_, reject) => {
27 | setTimeout(function() {
28 | act(() => {
29 | reject(new Error('an error'))
30 | })
31 | }, 49)
32 | })
33 | renderHook(() => {
34 | act(() => {
35 | expect(usePromise(prom)).toMatchSnapshot()
36 | })
37 |
38 | setTimeout(() => {
39 | done()
40 | }, 60)
41 | })
42 | })
43 |
44 | it('works when providing a promise thunk', (done) => {
45 | let promThunk = () =>
46 | new Promise((resolve) => {
47 | setTimeout(function() {
48 | act(() => {
49 | resolve('a value from promise returning thunk')
50 | })
51 | }, 49)
52 | })
53 |
54 | renderHook(() => {
55 | act(() => {
56 | expect(usePromise(promThunk)).toMatchSnapshot()
57 | })
58 |
59 | setTimeout(() => {
60 | done()
61 | }, 60)
62 | })
63 | })
64 | })
65 |
--------------------------------------------------------------------------------
/src/usePromise.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState, useRef } from 'react'
2 |
3 | export default function usePromise(
4 | promiseOrFn: (() => Promise) | Promise
5 | ): {
6 | loading: boolean
7 | error: Error | null
8 | value: T | undefined
9 | } {
10 | const [state, setState] = useState<{
11 | loading: boolean
12 | error: Error | null
13 | value: T | undefined
14 | }>({
15 | loading: !!promiseOrFn,
16 | error: null,
17 | value: undefined
18 | })
19 | const isMounted = useRef(false)
20 | useEffect(() => {
21 | isMounted.current = true
22 | if (!promiseOrFn) {
23 | setState({
24 | loading: false,
25 | error: null,
26 | value: undefined
27 | })
28 | } else {
29 | if (state.loading === false) {
30 | setState({
31 | loading: true,
32 | error: null,
33 | value: undefined
34 | })
35 | }
36 | let promise: Promise
37 | if (typeof promiseOrFn === 'function') {
38 | promise = promiseOrFn()
39 | } else {
40 | promise = promiseOrFn
41 | }
42 |
43 | promise
44 | .then((value) => {
45 | if (isMounted.current) {
46 | setState({
47 | loading: false,
48 | error: null,
49 | value
50 | })
51 | }
52 | })
53 | .catch((error) => {
54 | if (isMounted.current) {
55 | setState({
56 | loading: false,
57 | error,
58 | value: undefined
59 | })
60 | }
61 | })
62 | }
63 |
64 | return () => {
65 | isMounted.current = false
66 | }
67 | }, [promiseOrFn])
68 |
69 | return state
70 | }
71 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "lib": ["dom", "esnext"],
6 | "declaration": true,
7 | "sourceMap": true,
8 | // "rootDir": "./",
9 | "strict": true,
10 | "noImplicitAny": true,
11 | "strictNullChecks": true,
12 | "strictFunctionTypes": true,
13 | "strictPropertyInitialization": true,
14 | "noImplicitThis": true,
15 | "alwaysStrict": true,
16 | "noUnusedLocals": true,
17 | "noUnusedParameters": true,
18 | "noImplicitReturns": true,
19 | "noFallthroughCasesInSwitch": true,
20 | "moduleResolution": "node",
21 | "outDir": "./dist",
22 | "baseUrl": "./",
23 | "paths": {
24 | "*": ["src/*", "node_modules/*"]
25 | },
26 | "jsx": "react",
27 | "esModuleInterop": true
28 | },
29 | "include": ["src", "types"],
30 | "exclude": ["**/*.spec.ts"]
31 | }
32 |
--------------------------------------------------------------------------------