Skip to content

Commit cbf3cfa

Browse files
committed
feat(useFetch): Adding useFetch function
1 parent 8f77a5a commit cbf3cfa

File tree

11 files changed

+1327
-635
lines changed

11 files changed

+1327
-635
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ Vue.use(VueCompositionAPI)
119119
[![Demo](https://img.shields.io/badge/demo-🚀-yellow.svg)](https://microcipcip.github.io/vue-use-kit/?path=/story/side-effects-usebeforeunload--demo)
120120
- [`useCookie`](./src/functions/useCookie/stories/useCookie.md) — provides way to read, update and delete a cookie.
121121
[![Demo](https://img.shields.io/badge/demo-🚀-yellow.svg)](https://microcipcip.github.io/vue-use-kit/?path=/story/side-effects-usecookie--demo)
122+
- [`useFetch`](./src/functions/useFetch/stories/useFetch.md) — provides a way to fetch resources asynchronously across the network.
123+
[![Demo](https://img.shields.io/badge/demo-🚀-yellow.svg)](https://microcipcip.github.io/vue-use-kit/?path=/story/side-effects-usefetch--demo)
122124
- [`useLocalStorage`](./src/functions/useLocalStorage/stories/useLocalStorage.md) — provides way to read, update and delete a localStorage key.
123125
[![Demo](https://img.shields.io/badge/demo-🚀-yellow.svg)](https://microcipcip.github.io/vue-use-kit/?path=/story/side-effects-uselocalstorage--demo)
124126
- [`useSessionStorage`](./src/functions/useSessionStorage/stories/useSessionStorage.md) — provides way to read, update and delete a sessionStorage key.

package-lock.json

Lines changed: 975 additions & 631 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,10 @@
8989
"@commitlint/cli": "^7.1.2",
9090
"@commitlint/config-conventional": "^7.1.2",
9191
"@fortawesome/fontawesome-free": "^5.12.0",
92-
"@storybook/addon-notes": "^5.3.13",
93-
"@storybook/addon-viewport": "^5.3.13",
94-
"@storybook/theming": "^5.3.13",
95-
"@storybook/vue": "^5.3.13",
92+
"@storybook/addon-notes": "^5.3.18",
93+
"@storybook/addon-viewport": "^5.3.18",
94+
"@storybook/theming": "^5.3.18",
95+
"@storybook/vue": "^5.3.18",
9696
"@types/jest": "^23.3.2",
9797
"@types/node": "^10.11.0",
9898
"@types/throttle-debounce": "^2.1.0",

src/functions/useFetch/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './useFetch'
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<template>
2+
<div>
3+
<div class="actions">
4+
<button
5+
class="button is-primary"
6+
@click="startWithSuccess"
7+
:disabled="isLoading"
8+
v-text="isInit ? 'Fetch again!' : 'Fetch'"
9+
/>
10+
<button
11+
class="button is-info"
12+
@click="startWithFailed"
13+
:disabled="isLoading"
14+
v-text="`Fetch with failure`"
15+
/>
16+
<button
17+
class="button is-danger"
18+
@click="stop"
19+
:disabled="!isLoading"
20+
v-text="`Abort fetch`"
21+
/>
22+
</div>
23+
24+
<!-- isAborted -->
25+
<use-fetch-demo-table status="Aborted" v-if="isAborted">
26+
Resource fetch aborted with status code
27+
<strong>{{ status }}</strong> and message
28+
<strong>{{ statusText }}</strong>
29+
</use-fetch-demo-table>
30+
31+
<!-- isFailed -->
32+
<use-fetch-demo-table status="Failed" v-else-if="isFailed">
33+
Resource fetch failed with status code
34+
<strong>{{ status }}</strong> and message
35+
<strong>{{ statusText }}</strong>
36+
</use-fetch-demo-table>
37+
38+
<!-- isSuccess -->
39+
<div v-else>
40+
<!-- !isInit -->
41+
<use-fetch-demo-table status="Not initialized" v-if="!isInit">
42+
Click "Fetch" to initialize the request.
43+
</use-fetch-demo-table>
44+
45+
<!-- isLoading -->
46+
<use-fetch-demo-table status="Loading" v-else-if="isLoading">
47+
Resource is being fetched...
48+
</use-fetch-demo-table>
49+
50+
<!-- Fetched -->
51+
<use-fetch-demo-table status="Success" v-else>
52+
<p>
53+
Resource fetched successfully with status code
54+
<strong>{{ status }}</strong> and message
55+
<strong>{{ statusText }}</strong>
56+
</p>
57+
<img class="img" :src="data.message" alt="" />
58+
</use-fetch-demo-table>
59+
</div>
60+
</div>
61+
</template>
62+
63+
<script lang="ts">
64+
import Vue from 'vue'
65+
import { ref } from '@src/api'
66+
import { useFetch } from '@src/vue-use-kit'
67+
import UseFetchDemoTable from './UseFetchDemoTable.vue'
68+
69+
export default Vue.extend({
70+
name: 'UseFetchDemo',
71+
components: { UseFetchDemoTable },
72+
setup() {
73+
const isInit = ref(false)
74+
const slowApi = 'http://slowwly.robertomurray.co.uk/delay/1000/url'
75+
const randomDogUrl = `${slowApi}/https://dog.ceo/api/breeds/image/random`
76+
const url = ref(randomDogUrl)
77+
const {
78+
data,
79+
status,
80+
statusText,
81+
isLoading,
82+
isFailed,
83+
isAborted,
84+
start,
85+
stop
86+
} = useFetch(url, {}, false)
87+
88+
const startWithSuccess = () => {
89+
isInit.value = true
90+
url.value = randomDogUrl
91+
start()
92+
}
93+
94+
const startWithFailed = () => {
95+
isInit.value = true
96+
url.value = `${slowApi}/https://dog.ceo`
97+
start()
98+
}
99+
100+
return {
101+
data,
102+
status,
103+
statusText,
104+
isInit,
105+
isLoading,
106+
isFailed,
107+
isAborted,
108+
startWithSuccess,
109+
startWithFailed,
110+
stop
111+
}
112+
}
113+
})
114+
</script>
115+
116+
<style scoped>
117+
.actions {
118+
padding-bottom: 20px;
119+
}
120+
121+
.img {
122+
display: block;
123+
margin: 20px 0 0;
124+
max-width: 300px;
125+
border-radius: 3px;
126+
}
127+
</style>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<template>
2+
<table class="table is-fullwidth">
3+
<thead>
4+
<tr>
5+
<th>Status</th>
6+
<th>Message</th>
7+
</tr>
8+
</thead>
9+
<tbody>
10+
<tr>
11+
<td v-text="status" />
12+
<td width="100%"><slot></slot></td>
13+
</tr>
14+
</tbody>
15+
</table>
16+
</template>
17+
18+
<script lang="ts">
19+
import Vue from 'vue'
20+
21+
export default Vue.extend({
22+
name: 'UseFetchDemoTable',
23+
props: {
24+
status: String
25+
}
26+
})
27+
</script>
28+
29+
<style scoped></style>
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# useFetch
2+
3+
Vue function that fetch resources asynchronously across the network.
4+
5+
## Reference
6+
7+
```typescript
8+
type TUseFetchUrl = RequestInfo | Ref<RequestInfo>
9+
```
10+
11+
```typescript
12+
function useFetch(
13+
url: TUseFetchUrl,
14+
options?: RequestInit,
15+
runOnMount?: boolean
16+
): {
17+
data: Ref<any>
18+
status: Ref<number | null>
19+
statusText: Ref<string | null>
20+
isLoading: Ref<boolean>
21+
isFailed: Ref<boolean>
22+
isAborted: Ref<boolean>
23+
start: () => Promise<void>
24+
stop: () => void
25+
}
26+
```
27+
28+
### Parameters
29+
30+
- `url: TUseFetchUrl` the fetch url value, can be type string or type `RequestInfo`.
31+
- `options: RequestInit` the fetch url options.
32+
- `runOnMount: boolean` whether to fetch on mount, `true` by default.
33+
34+
### Returns
35+
36+
- `data: Ref<any>` the response data, has to be of JSON type otherwise will return an error
37+
- `status: Ref<number | null>` the status code of the response
38+
- `statusText: Ref<string | null>` the status text of the response
39+
- `isLoading: Ref<boolean>` whether fetch request is loading or not
40+
- `isFailed: Ref<boolean>` whether fetch request has failed or not
41+
- `isAborted: Ref<boolean>` whether fetch request has been aborted or not
42+
- `start: Function` the function used for starting fetch request
43+
- `stop: Function` the function used for aborting fetch request
44+
45+
## Usage
46+
47+
```html
48+
<template>
49+
<div>
50+
<div v-if="isFailed">Failed!</div>
51+
<div v-else-if="isLoading">Loading...</div>
52+
<div v-else><img :src="data.message" alt="" /></div>
53+
</div>
54+
</template>
55+
56+
<script lang="ts">
57+
import Vue from 'vue'
58+
import { useFetch } from 'vue-use-kit'
59+
60+
export default Vue.extend({
61+
name: 'UseFetchDemo',
62+
setup() {
63+
const url = 'https://dog.ceo/api/breeds/image/random'
64+
const { data, isLoading, isFailed } = useFetch(url)
65+
return { data, isLoading, isFailed }
66+
}
67+
})
68+
</script>
69+
```
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { storiesOf } from '@storybook/vue'
2+
import path from 'path'
3+
import StoryTitle from '@src/helpers/StoryTitle.vue'
4+
import UseFetchDemo from './UseFetchDemo.vue'
5+
6+
const functionName = 'useFetch'
7+
const functionPath = path.resolve(__dirname, '..')
8+
const notes = require(`./${functionName}.md`).default
9+
10+
const basicDemo = () => ({
11+
components: { StoryTitle, demo: UseFetchDemo },
12+
template: `
13+
<div class="container">
14+
<story-title
15+
function-path="${functionPath}"
16+
source-name="${functionName}"
17+
demo-name="UseFetchDemo.vue"
18+
>
19+
<template v-slot:title></template>
20+
<template v-slot:intro></template>
21+
</story-title>
22+
<demo />
23+
</div>`
24+
})
25+
26+
storiesOf('side effects|useFetch', module)
27+
.addParameters({ notes })
28+
.add('Demo', basicDemo)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// import { mount } from '@src/helpers/test'
2+
// import { useFetch } from '@src/vue-use-kit'
3+
4+
afterEach(() => {
5+
jest.clearAllMocks()
6+
})
7+
8+
describe('useFetch', () => {
9+
it('should do something', () => {
10+
// Add test here
11+
})
12+
})

src/functions/useFetch/useFetch.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { ref, onMounted, onUnmounted, Ref, isRef } from '@src/api'
2+
3+
export type TUseFetchUrl = RequestInfo | Ref<RequestInfo>
4+
5+
const abortError = 'AbortError'
6+
7+
const isContentTypeJson = (res: Response) => {
8+
const contentType = res.headers.get('content-type')
9+
return contentType && contentType.includes('application/json')
10+
}
11+
12+
const getUrl = (url: TUseFetchUrl) => (isRef(url) ? url.value : url)
13+
14+
const fetchWrapper = async (url: TUseFetchUrl, opts: RequestInit) => {
15+
const res = await fetch(getUrl(url), opts)
16+
const resData = isContentTypeJson(res) && (await res.json())
17+
return { resData, res }
18+
}
19+
20+
export function useFetch(
21+
url: TUseFetchUrl,
22+
options: RequestInit = {},
23+
runOnMount = true
24+
) {
25+
const data: Ref<any> = ref(null)
26+
const status: Ref<null | number> = ref(null)
27+
const statusText: Ref<null | string> = ref(null)
28+
const isLoading = ref(false)
29+
const isFailed = ref(false)
30+
const isAborted = ref(false)
31+
32+
let controller: AbortController
33+
34+
const start = async () => {
35+
try {
36+
controller = new AbortController()
37+
const signal = controller.signal
38+
39+
isLoading.value = true
40+
isFailed.value = false
41+
isAborted.value = false
42+
43+
const { resData, res } = await fetchWrapper(getUrl(url), {
44+
signal,
45+
...options
46+
})
47+
data.value = resData
48+
49+
isLoading.value = false
50+
isFailed.value = !res.ok
51+
status.value = res.status
52+
statusText.value = res.statusText
53+
} catch (err) {
54+
if (err.name === abortError) isAborted.value = true
55+
isLoading.value = false
56+
isFailed.value = true
57+
status.value = 500
58+
statusText.value = err.message || err.name
59+
}
60+
}
61+
62+
const stop = () => {
63+
if (controller) controller.abort()
64+
}
65+
66+
onMounted(() => runOnMount && start())
67+
onUnmounted(stop)
68+
69+
return {
70+
data,
71+
status,
72+
statusText,
73+
isLoading,
74+
isFailed,
75+
isAborted,
76+
start,
77+
stop
78+
}
79+
}

src/vue-use-kit.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,6 @@ export * from './functions/useTimeout'
3131
// Site Effects
3232
export * from './functions/useBeforeUnload'
3333
export * from './functions/useCookie'
34+
export * from './functions/useFetch'
3435
export * from './functions/useLocalStorage'
3536
export * from './functions/useSessionStorage'

0 commit comments

Comments
 (0)