Skip to content

Commit 323fa78

Browse files
authored
Abort and demos (#14)
* abort and demos * maybe idempotent-babel-polyfill * remove dist * Update README.md * wrapping up, finished demos, pulled out unused code * v0.1.50 * v0.1.51 * making it so dist is only included when we publish * switch package name, adding build step in publish * cleaning up examples * Update README.md * Update README.md * Create remove.js * Add files via upload * Delete remove.js * Update README.md
1 parent f5831b8 commit 323fa78

File tree

9 files changed

+311
-328
lines changed

9 files changed

+311
-328
lines changed

README.md

+10-4
Original file line numberDiff line numberDiff line change
@@ -93,17 +93,21 @@ patch({
9393
})
9494
```
9595
96-
#### Coming Soon: `abort`
96+
#### Abort
97+
98+
<img src="public/abort-example-1.gif" height="250" />
99+
97100
98101
```jsx
99102
const githubRepos = useFetch({
100-
baseUrl: `https://api.github.com/search/repositories`
103+
baseUrl: `https://api.github.com/search/repositories?q=`
101104
})
102105

103-
const searchGithub = e => githubRepos.get(`?q=${e.target.value || "''"}`)
106+
// the line below is not isomorphic, but for simplicity we're using the browsers `encodeURI`
107+
const searchGithubRepos = e => githubRepos.get(encodeURI(e.target.value))
104108

105109
<>
106-
<input onChange={searchGithub} />
110+
<input onChange={searchGithubRepos} />
107111
<button onClick={githubRepos.abort}>Abort</button>
108112
{githubRepos.loading ? 'Loading...' : githubRepos.data.items.map(repo => (
109113
<div key={repo.id}>{repo.name}</div>
@@ -183,6 +187,8 @@ Todos
183187
- [ ] Allow option to fetch on server instead of just having `loading` state
184188
- [ ] Allow option for callback for response.json() vs response.text()
185189
- [ ] add `timeout`
190+
- [ ] add `debounce`
191+
- [ ] if 2nd param of `post` or one of the methods is a `string` treat it as query params
186192
- [ ] error handling if no url is passed
187193
- [ ] tests
188194
- [ ] port to typescript

examples/abort-examples.js

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import React from 'react'
2+
import useFetch from '../src/index'
3+
import { delayedUrl } from './urls'
4+
5+
/**
6+
* Abortable demos
7+
* - https://github.com/dai-shi/react-hooks-async
8+
*/
9+
10+
const GithubRepoSearchAbortDemo = () => {
11+
const githubQueryRepoURL = 'https://api.github.com/search/repositories?q='
12+
const githubRepos = useFetch({
13+
baseUrl: githubQueryRepoURL,
14+
})
15+
16+
const searchGithub = e => githubRepos.get(e.target.value || "%27%27")
17+
18+
const githubRepoItems = (githubRepos.data || {}).items || []
19+
20+
return (
21+
<>
22+
<h3>Github Repo Search Demo</h3>
23+
<input onChange={searchGithub} />
24+
{githubRepos.loading ? (
25+
<div style={{display: 'flex'}}>
26+
Loading...
27+
<button onClick={githubRepos.abort}>Cancel Request</button>
28+
</div>
29+
) : githubRepoItems.map(({ id, name, html_url }) => (
30+
<li key={id}><a target="_blank" rel="noreferrer noopener" href={html_url}>{name}</a></li>
31+
))}
32+
</>
33+
)
34+
}
35+
36+
const OnClickAbortDemo = () => {
37+
const request = useFetch(delayedUrl)
38+
return (
39+
<>
40+
<h3>On Click Demo</h3>
41+
<button onClick={() => request.post({ no: 'way' })}>Fetch Data</button>
42+
{request.loading ? (
43+
<div style={{display: 'flex'}}>
44+
Loading...
45+
<button onClick={request.abort}>Cancel Request</button>
46+
</div>
47+
) : (
48+
<code style={{ display: 'block' }}>
49+
<pre>{JSON.stringify(request.data, null, 2)}</pre>
50+
</code>
51+
)}
52+
</>
53+
)
54+
}
55+
56+
const AbortDemos = () => (
57+
<>
58+
<div>If for some reason you aren't getting any responses from these, it's possible you have used your daily limit for api calls to these apis.</div>
59+
<h1>Abort Demos</h1>
60+
<p>Open the network tab in the devtools to see this in action 😛</p>
61+
<GithubRepoSearchAbortDemo />
62+
<OnClickAbortDemo />
63+
</>
64+
)
65+
66+
export default AbortDemos

examples/index.js

+9-122
Original file line numberDiff line numberDiff line change
@@ -1,126 +1,13 @@
1-
import React from 'react';
2-
import { render } from 'react-dom';
3-
// import './index.css';
4-
// import App from './App';
5-
// import * as serviceWorker from './serviceWorker';
6-
import useFetch, { useGet, usePost } from '../src/index'
7-
// import useFetch, { useGet, usePost } from '../dist'
8-
9-
const App = () => {
10-
const [data, loading, error, { get }] = useFetch('https://api.etilbudsavis.dk/v2/dealerfront?country_id=DK')
11-
const handleClick = () => get()
12-
13-
// WORKS 😍
14-
// const [data, loading, error, request] = useFetch({
15-
// url: 'https://jsonplaceholder.typicode.com/posts/1',
16-
// // baseUrl: '', // then you can do: request.post({ url: '/posts', body: {} })
17-
// headers: {
18-
// "Content-type": "application/json; charset=UTF-8"
19-
// },
20-
// // timeout: 1000,
21-
// // onMount: true, // run on component did mount
22-
// })
23-
24-
// const [data, loading, error, request] = useFetch('https://jsonplaceholder.typicode.com/posts/1')
25-
26-
// const { data, loading, error, request, get, post, patch, put, del } = useFetch('https://jsonplaceholder.typicode.com/posts/1')
27-
// const { data, loading, error, request, get, post, patch, put, del } = useFetch({
28-
// baseUrl: 'https://jsonplaceholder.typicode.com/posts'
29-
// })
30-
31-
// useEffect(() => {
32-
// // on component did mount or use 'onMount' option above
33-
// request.get()
34-
// }, [request])
35-
36-
// const [data, loading, error, get] = useGet({
37-
// url: 'https://jsonplaceholder.typicode.com/posts/1'
38-
// })
39-
const handleClick2 = () => {
40-
// get('/1')
41-
// post('/', {
42-
// // params: '?no=way&something=true',
43-
// title: 'foo',
44-
// body: 'bar',
45-
// userId: 1
46-
// })
47-
// patch('/1', {
48-
// title: 'foo',
49-
// body: 'bar',
50-
// userId: 1
51-
// })
52-
// put('/1', {
53-
// title: 'foo',
54-
// body: 'bar',
55-
// userId: 1
56-
// })
57-
// del('/1')
58-
// request.get()
59-
// request.post({
60-
// // params: '?no=way&something=true',
61-
// title: 'foo',
62-
// body: 'bar',
63-
// userId: 1
64-
// })
65-
// request.patch({
66-
// title: 'foo',
67-
// body: 'bar',
68-
// userId: 1
69-
// })
70-
// request.put({
71-
// title: 'foo',
72-
// body: 'bar',
73-
// userId: 1
74-
// })
75-
// request.delete({
76-
// title: 'foo',
77-
// body: 'bar',
78-
// userId: 1
79-
// })
80-
// get()
81-
// post({
82-
// // params: '?no=way&something=true',
83-
// title: 'foo',
84-
// body: 'bar',
85-
// userId: 1
86-
// })
87-
// patch({
88-
// title: 'foo',
89-
// body: 'bar',
90-
// userId: 1
91-
// })
92-
// put({
93-
// title: 'foo',
94-
// body: 'bar',
95-
// userId: 1
96-
// })
97-
// del({
98-
// title: 'foo',
99-
// body: 'bar',
100-
// userId: 1
101-
// })
102-
}
103-
104-
105-
if (error) return 'Error...'
106-
if (loading) return 'Loading...'
1+
import React, { useState, Fragment, useRef } from 'react'
2+
import { render } from 'react-dom'
3+
import AbortExamples from './abort-examples'
1074

5+
function App() {
1086
return (
109-
<div className="App">
110-
<header className="App-header">
111-
<button onClick={handleClick}>CLICK</button>
112-
<code style={{ display: 'block' }}>
113-
<pre>{JSON.stringify(data, null, 2)}</pre>
114-
</code>
115-
</header>
116-
</div>
117-
);
7+
<>
8+
<AbortExamples />
9+
</>
10+
)
11811
}
11912

120-
render(<App />, document.getElementById('root'));
121-
122-
// If you want your app to work offline and load faster, you can change
123-
// unregister() to register() below. Note this comes with some pitfalls.
124-
// Learn more about service workers: https://bit.ly/CRA-PWA
125-
// serviceWorker.unregister();
126-
//
13+
render(<App />, document.getElementById('root'))

examples/urls.js

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
export const delayedUrl = 'https://httpbin.org/delay/3'
3+
export const chuckUrl = 'https://api.icndb.com/jokes/random/%3FlimitTo=[nerdy]&escape=javascript' // - handles POST too
4+
// export const allUrl = 'https://km04k9k9x5.codesandbox.io/test.json'
5+
// const baseUrl = 'https://jsonplaceholder.typicode.com'
6+
// const postUrl = baseUrl + '/posts'
7+
// POST https://jsonplaceholder.typicode.com/posts
8+
// const allUrl = postUrl + '/1'
9+
// everything else: https://jsonplaceholder.typicode.com/posts/1

package.json

+9-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"name": "use-http-test",
3-
"version": "0.1.49",
2+
"name": "use-http",
3+
"version": "0.1.57",
44
"homepage": "https://codesandbox.io/embed/km04k9k9x5",
55
"main": "dist/index.js",
66
"keywords": [
@@ -22,21 +22,25 @@
2222
"react": "^16.8.0"
2323
},
2424
"dependencies": {
25+
"idempotent-babel-polyfill": "^7.0.0",
2526
"react": "^16.8.6"
2627
},
2728
"devDependencies": {
2829
"@babel/core": "^7.4.3",
2930
"@babel/preset-env": "^7.4.3",
3031
"@babel/preset-react": "^7.0.0",
3132
"babel-jest": "^24.7.1",
32-
"idempotent-babel-polyfill": "^7.0.0",
3333
"jest": "^24.7.1",
3434
"parcel-bundler": "^1.12.3",
3535
"react-dom": "^16.8.6"
3636
},
3737
"scripts": {
3838
"dev": "parcel examples/index.html --open",
3939
"build": "parcel build --target node ./src/*",
40-
"test": "jest"
41-
}
40+
"test": "jest",
41+
"publish": "yarn build && yarn publish"
42+
},
43+
"files": [
44+
"dist"
45+
]
4246
}

public/abort-example-1.gif

2.63 MB
Loading

public/abort-example-2.gif

1.73 MB
Loading

src/useFetch.js

+16-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import 'idempotent-babel-polyfill' // so async await works ;)
2-
import { useEffect, useState, useCallback } from 'react'
2+
import { useEffect, useState, useCallback, useRef } from 'react'
33

44
const isObject = obj => Object.prototype.toString.call(obj) === '[object Object]'
55

@@ -33,8 +33,14 @@ export function useFetch(arg1, arg2) {
3333
const [data, setData] = useState(null)
3434
const [loading, setLoading] = useState(onMount)
3535
const [error, setError] = useState(null)
36+
const controller = useRef(null)
3637

3738
const fetchData = useCallback(method => async (fArg1, fArg2) => {
39+
if ('AbortController' in window) {
40+
controller.current = new AbortController()
41+
options.signal = controller.current.signal
42+
}
43+
3844
let query = ''
3945
if (isObject(fArg1) && method.toLowerCase() !== 'get') {
4046
options.body = JSON.stringify(fArg1)
@@ -48,7 +54,7 @@ export function useFetch(arg1, arg2) {
4854
setLoading(true)
4955
const response = await fetch(url + query, {
5056
method,
51-
...options,
57+
...options
5258
})
5359
let data = null
5460
try {
@@ -58,8 +64,9 @@ export function useFetch(arg1, arg2) {
5864
}
5965
setData(data)
6066
} catch (err) {
61-
setError(err)
67+
if (err.name !== 'AbortError') setError(err)
6268
} finally {
69+
controller.current = null
6370
setLoading(false)
6471
}
6572
},
@@ -72,13 +79,17 @@ export function useFetch(arg1, arg2) {
7279
const put = useCallback(fetchData('PUT'))
7380
const del = useCallback(fetchData('DELETE'))
7481

75-
const request = { get, post, patch, put, del, delete: del }
82+
const abort = () => {
83+
controller.current && controller.current.abort()
84+
}
85+
86+
const request = { get, post, patch, put, del, delete: del, abort }
7687

7788
useEffect(() => {
7889
if (onMount) request[method.toLowerCase()]()
7990
}, [])
8091

81-
return Object.assign([data, loading, error, request], { data, loading, error, request, ...request })
92+
return Object.assign([data, loading, error, request], { data, loading, error, request, abort, ...request })
8293
}
8394

8495
export default useFetch

0 commit comments

Comments
 (0)