Skip to content
This repository was archived by the owner on Jul 12, 2023. It is now read-only.

Commit fef4875

Browse files
committed
support metadata
1 parent efca5ea commit fef4875

File tree

5 files changed

+361
-28
lines changed

5 files changed

+361
-28
lines changed

README.md

Lines changed: 87 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,22 @@ the Medium article
99

1010
## Install
1111

12-
* `npm install fetch-suspense --save` or
12+
* `npm install fetch-suspense` or
1313
* `yarn add fetch-suspense`
1414

15-
## Example
15+
## Examples
1616

17-
```JavaScript
17+
### Basic Example
18+
19+
```javascript
1820
import useFetch from 'fetch-suspense';
1921
import React, { Suspense } from 'react';
2022

2123
// This fetching component will be delayed by Suspense until the fetch request
22-
// resolves.
23-
// The return value of useFetch will be the response of the server.
24+
// resolves. The return value of useFetch will be the response of the server.
2425
const MyFetchingComponent = () => {
25-
const data = useFetch('/path/to/api', { method: 'POST' });
26-
return 'The server responded with: ' + data;
26+
const response = useFetch('/path/to/api', { method: 'POST' });
27+
return 'The server responded with: ' + response;
2728
};
2829

2930
// The App component wraps the asynchronous fetching component in Suspense.
@@ -38,12 +39,12 @@ const App = () => {
3839
};
3940
```
4041

41-
## Using a Custom Fetch API
42+
### Using a Custom Fetch API
4243

4344
If you don't want to rely on the global `fetch` API, you can create your own
4445
`useFetch` hook by importing the `createUseFetch` helper function.
4546

46-
```JavaScript
47+
```javascript
4748
import { createUseFetch } from 'fetch-suspense';
4849
import myFetchApi from 'my-fetch-package';
4950
import React, { Suspense } from 'react';
@@ -54,11 +55,43 @@ import React, { Suspense } from 'react';
5455
const useFetch = createUseFetch(myFetchApi);
5556

5657
// This fetching component will be delayed by Suspense until the fetch request
58+
// resolves. The return value of useFetch will be the response of the server.
59+
const MyFetchingComponent = () => {
60+
const response = useFetch('/path/to/api', { method: 'POST' });
61+
return 'The server responded with: ' + response;
62+
};
63+
64+
// The App component wraps the asynchronous fetching component in Suspense.
65+
// The fallback component (loading text) is displayed until the fetch request
5766
// resolves.
58-
// The return value of useFetch will be the response of the server.
67+
const App = () => {
68+
return (
69+
<Suspense fallback="Loading...">
70+
<MyFetchingComponent />
71+
</Suspense>
72+
);
73+
};
74+
```
75+
76+
### Including Fetch Metadata
77+
78+
To include fetch metadata with your response, include an `options` parameter
79+
with `metadata: true`.
80+
81+
```javascript
82+
import useFetch from 'fetch-suspense';
83+
import React, { Suspense } from 'react';
84+
85+
// This fetching component will be delayed by Suspense until the fetch request
86+
// resolves. The return value of useFetch will be the response of the server
87+
// AS WELL AS metadata for the request.
5988
const MyFetchingComponent = () => {
60-
const data = useFetch('/path/to/api', { method: 'POST' });
61-
return 'The server responded with: ' + data;
89+
const { contentType, response } = useFetch(
90+
'/path/to/api',
91+
{ method: 'POST' },
92+
{ metadata: true }, // <--
93+
);
94+
return `The server responded with ${contentType}: ${response}`;
6295
};
6396

6497
// The App component wraps the asynchronous fetching component in Suspense.
@@ -72,3 +105,45 @@ const App = () => {
72105
);
73106
};
74107
```
108+
109+
## Options
110+
111+
The supported options for the third, options parameter are:
112+
113+
### lifespan?: number
114+
115+
_Default: 0_
116+
117+
The number of milliseconds to cache the result of the request. Each time the
118+
component mounts before this many milliseconds have passed, it will return the
119+
response from the last time this same request was made.
120+
121+
If 0, the cache will be last the remainder of the browser session.
122+
123+
### metadata?: boolean
124+
125+
_Default: false_
126+
127+
If true, the `useFetch` hook will return metadata _in addition to_ the response
128+
from the fetch request. Instead of returning just the response, an interface
129+
as follows will be returned:
130+
131+
```typescript
132+
interface UseFetchResponse {
133+
bodyUsed: boolean;
134+
contentType: null | string;
135+
headers: Headers;
136+
ok: boolean;
137+
redirected: boolean;
138+
// The same response from the server that would be returned if metadata were
139+
// false. It is an Object is the server responded with JSON, and it is a
140+
// string if the server responded with plain text.
141+
response: Object | string;
142+
status: number;
143+
statusText: string;
144+
url: string;
145+
}
146+
```
147+
148+
You can access these properties easily through destructuring. See
149+
[Including Fetch Metadata](#including-fetch-metadata).

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "fetch-suspense",
3-
"version": "1.1.0",
3+
"version": "1.2.0",
44
"author": "Charles Stover <[email protected]>",
55
"bugs": {
66
"url": "https://github.com/CharlesStover/fetch-suspense/issues"
@@ -26,11 +26,13 @@
2626
"@types/deep-equal": "^1.0.1",
2727
"@types/mocha": "^5.2.6",
2828
"@types/node": "^12.6.2",
29+
"@types/node-fetch": "^2.5.0",
2930
"@types/sinon": "^7.0.11",
3031
"chai": "^4.2.0",
3132
"jsdom": "^14.0.0",
3233
"jsdom-global": "^3.0.2",
3334
"mocha": "^6.1.3",
35+
"node-fetch": "^2.6.0",
3436
"sinon": "^7.3.1",
3537
"ts-node": "^8.0.3",
3638
"typescript": "^3.1.5"

src/fetch-suspense.ts

Lines changed: 95 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,60 @@ interface Export extends UseFetch {
1010
}
1111

1212
interface FetchCache {
13+
bodyUsed?: boolean;
14+
contentType?: null | string;
1315
fetch?: Promise<void>;
1416
error?: any;
17+
headers?: Headers;
1518
init: RequestInit | undefined;
1619
input: RequestInfo;
20+
ok?: boolean;
21+
redirected?: boolean;
1722
response?: any;
23+
status?: number;
24+
statusText?: string;
25+
url?: string;
1826
}
1927

20-
type UseFetch = (
21-
input: RequestInfo,
22-
init?: RequestInit | undefined,
23-
lifespan?: number,
24-
) => Object | string;
28+
type FetchResponse = Object | string;
29+
30+
interface FetchResponseMetadata {
31+
bodyUsed: boolean;
32+
contentType: null | string;
33+
headers: Headers;
34+
ok: boolean;
35+
redirected: boolean;
36+
response: FetchResponse;
37+
status: number;
38+
statusText: string;
39+
url: string;
40+
}
41+
42+
interface Options {
43+
lifespan?: number;
44+
metadata?: boolean;
45+
}
46+
47+
interface OptionsWithMetadata extends Options {
48+
metadata: true;
49+
}
50+
51+
interface OptionsWithoutMetadata extends Options {
52+
metadata?: false;
53+
}
54+
55+
interface UseFetch {
56+
(
57+
input: RequestInfo,
58+
init?: RequestInit | undefined,
59+
options?: number | OptionsWithoutMetadata,
60+
): FetchResponse;
61+
(
62+
input: RequestInfo,
63+
init: RequestInit | undefined,
64+
options: OptionsWithMetadata,
65+
): FetchResponseMetadata;
66+
}
2567

2668

2769

@@ -32,11 +74,27 @@ const createUseFetch: CreateUseFetch = (
3274
// Create a set of caches for this hook.
3375
const caches: FetchCache[] = [];
3476

35-
return (
77+
function useFetch(
3678
input: RequestInfo,
3779
init?: RequestInit | undefined,
38-
lifespan: number = 0,
39-
): Object | string => {
80+
options?: number | OptionsWithoutMetadata,
81+
): FetchResponse;
82+
function useFetch(
83+
input: RequestInfo,
84+
init: RequestInit | undefined,
85+
options: OptionsWithMetadata,
86+
): FetchResponseMetadata;
87+
function useFetch(
88+
input: RequestInfo,
89+
init?: RequestInit | undefined,
90+
options: number | Options = 0,
91+
): FetchResponse | FetchResponseMetadata {
92+
93+
if (typeof options === 'number') {
94+
return useFetch(input, init, { lifespan: options });
95+
}
96+
97+
const { metadata = false, lifespan = 0 } = options;
4098

4199
// Check each cache by this useFetch hook.
42100
for (const cache of caches) {
@@ -55,6 +113,19 @@ const createUseFetch: CreateUseFetch = (
55113

56114
// If a response was successful, return it.
57115
if (Object.prototype.hasOwnProperty.call(cache, 'response')) {
116+
if (metadata) {
117+
return {
118+
bodyUsed: cache.bodyUsed,
119+
contentType: cache.contentType,
120+
headers: cache.headers,
121+
ok: cache.ok,
122+
redirected: cache.redirected,
123+
response: cache.response,
124+
status: cache.status,
125+
statusText: cache.statusText,
126+
url: cache.url,
127+
};
128+
}
58129
return cache.response;
59130
}
60131

@@ -71,20 +142,27 @@ const createUseFetch: CreateUseFetch = (
71142
fetch: fetch(input, init)
72143

73144
// Parse the response.
74-
.then((response: Response): Promise<Object | string> => {
75-
const contentType: null | string =
76-
response.headers.get('Content-Type');
145+
.then((response: Response): Promise<FetchResponse> => {
146+
cache.contentType = response.headers.get('Content-Type');
147+
if (metadata) {
148+
cache.bodyUsed = response.bodyUsed;
149+
cache.headers = response.headers;
150+
cache.ok = response.ok;
151+
cache.redirected = response.redirected;
152+
cache.status = response.status;
153+
cache.statusText = response.statusText;
154+
}
77155
if (
78-
contentType &&
79-
contentType.indexOf('application/json') !== -1
156+
cache.contentType &&
157+
cache.contentType.indexOf('application/json') !== -1
80158
) {
81159
return response.json();
82160
}
83161
return response.text();
84162
})
85163

86164
// Cache the response.
87-
.then((response: Object | string): void => {
165+
.then((response: FetchResponse): void => {
88166
cache.response = response;
89167
})
90168

@@ -112,7 +190,9 @@ const createUseFetch: CreateUseFetch = (
112190
};
113191
caches.push(cache);
114192
throw cache.fetch;
115-
};
193+
}
194+
195+
return useFetch;
116196
};
117197

118198
const _export: Export = Object.assign(

0 commit comments

Comments
 (0)