Skip to content

Commit 1a33c45

Browse files
authored
feat(core): raw response support (#51)
* feat(core): add raw response support * test: updated tests * improvement(example): updated example with 'raw' options usage * fix(core): fix wrong logic; never using cached json * test(core): added some tests for raw response * docs: updated docs to include DataOptions.raw * test: add tests for preloading raw data * chore: some cleanups
1 parent f7cafc3 commit 1a33c45

15 files changed

Lines changed: 317 additions & 30 deletions

File tree

docusaurus/docs/others/data-options.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ Determines the how the data is handled. A more detailed explanation can be found
6666

6767
Determines whether the data should be fetched or not.
6868

69+
#### `raw`
70+
* Type: `boolean`
71+
* Default: `false`
72+
73+
Determines whether to return the `data` as raw response string, or JSON. For example, setting this to `true` will cause the `data` to be a string like `{\"message\":\"Hello world!\"}`.
74+
6975
#### `prefetch`
7076
* Type: `boolean`
7177
* Default: `false`

examples/ssr/src/ComponentUsingHook.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useData } from 'react-isomorphic-data';
33

44
const Comp = () => {
55
const { data } = useData(
6-
'http://localhost:3000/some-rest-api/24-hook',
6+
'http://localhost:3000/some-rest-api/1/24-hook',
77
{},
88
{}, // options that can be accepted by the native `fetch` API
99
{

examples/ssr/src/Home.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,7 @@ const Home = () => {
3030

3131
const eagerData = useData(
3232
'http://localhost:3000/some-rest-api/1',
33-
{
34-
foo: 'bar',
35-
symbols: '!@#$%^&*()////\\\\\\+_+_+_+-==~`'
36-
},
33+
{},
3734
{
3835
headers: {
3936
'x-custom-header': 'will only be sent for some-rest-api/1 request',
@@ -47,7 +44,10 @@ const Home = () => {
4744

4845
const [fetchData, lazyData] = useLazyData(
4946
'http://localhost:3000/some-rest-api/2',
50-
{},
47+
{
48+
foo: 'bar',
49+
symbols: '!@#$%^&*()////\\\\\\+_+_+_+-==~`'
50+
},
5151
{},
5252
{
5353
fetchPolicy: 'network-only',

examples/ssr/src/routes/Siblings/components/SFCWithHook.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import * as React from 'react';
22
import { useData } from 'react-isomorphic-data';
33

4-
const SFCWithHook: React.SFC<{ id: number }> = ({ id }) => {
4+
const SFCWithHook: React.SFC<{ id: number; raw?: boolean }> = ({
5+
id,
6+
raw = false,
7+
}) => {
58
const { data, loading, refetch } = useData(
69
`http://localhost:3000/some-rest-api/8-24-siblings-${id}`,
710
{},
@@ -10,15 +13,19 @@ const SFCWithHook: React.SFC<{ id: number }> = ({ id }) => {
1013
// additional options
1114
ssr: true,
1215
fetchPolicy: 'cache-first',
16+
raw,
1317
},
1418
);
1519

16-
console.log({ id, data, loading });
20+
console.log({ id, data, loading, raw });
1721

1822
return (
1923
<>
20-
<h1>SFCWithHook, id: {id} <button onClick={refetch}>Refetch</button></h1>
21-
{data ? data.randomNumber : 'loading...'}
24+
<h1>
25+
SFCWithHook, id: {id} raw: {String(raw)}{' '}
26+
<button onClick={refetch}>Refetch</button>
27+
</h1>
28+
{data ? (raw ? data : data.randomNumber) : 'loading...'}
2229
{String(loading)}
2330
</>
2431
);

examples/ssr/src/routes/Siblings/views/main.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ const SiblingsMainView: React.SFC = () => {
55
return (
66
<>
77
<SFCWithHook id={1} />
8+
<SFCWithHook id={1} />
9+
<SFCWithHook id={1} raw />
810
<SFCWithHook id={2} />
911
<SFCWithHook id={3} />
10-
<SFCWithHook id={1} />
1112
</>
1213
);
1314
};

examples/ssr/src/server.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ server.get('/*', async (req: express.Request, res: express.Response) => {
9999
// pass the same dataClient instance you are passing to your provider here
100100
try {
101101
const start = process.hrtime();
102-
markup = await renderToStringWithData(reactApp, dataClient);
102+
markup = await renderToStringWithData(reactApp);
103103
time.push(process.hrtime(start));
104104

105105
getResult();
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { FetchMock } from 'jest-fetch-mock';
2+
import preloadData from '../preloadData';
3+
import { createDataClient } from '../Client';
4+
5+
const fetchMock = fetch as FetchMock;
6+
7+
describe('preloadData tests', () => {
8+
beforeEach(() => {
9+
fetchMock.resetMocks();
10+
jest.useFakeTimers();
11+
});
12+
afterEach(() => {
13+
jest.useRealTimers();
14+
});
15+
16+
it('Should throw a promise when data is not ready yet', async () => {
17+
fetchMock.mockResponse(JSON.stringify({ message: 'Hello world!' }));
18+
const client = createDataClient({ ssr: false });
19+
const url = 'http://localhost:3000/some-rest-api-raw';
20+
21+
const resource = preloadData(client, url, {}, {}, { raw: true });
22+
23+
expect(resource.read).toThrow();
24+
25+
try {
26+
resource.read();
27+
} catch (promise) {
28+
await promise;
29+
30+
expect(resource.read()).toStrictEqual('{\"message\":\"Hello world!\"}');
31+
}
32+
});
33+
34+
it('Should throw an error the fetch is rejected', async () => {
35+
fetchMock.mockReject(() => Promise.reject('Fabricated error'));
36+
37+
const client = createDataClient({ ssr: false });
38+
const url = 'http://localhost:3000/some-rest-api-raw/2';
39+
40+
const resource = preloadData(client, url, {}, undefined, { raw: true });
41+
42+
expect(resource.read).toThrow();
43+
44+
try {
45+
resource.read();
46+
} catch (promise) {
47+
await promise;
48+
49+
expect(resource.read).toThrow();
50+
expect(true).toBe(true);
51+
}
52+
});
53+
54+
it('Should use data from cache if available', async () => {
55+
fetchMock.mockResponse(JSON.stringify({ message: 'Hello world!' }));
56+
const client = createDataClient({ ssr: false });
57+
const url = 'http://localhost:3000/some-rest-api-raw/3';
58+
59+
const resource = preloadData(client, url, {}, {}, { raw: true });
60+
61+
expect(resource.read).toThrow();
62+
63+
try {
64+
resource.read();
65+
} catch (promise) {
66+
await promise;
67+
68+
expect(resource.read()).toStrictEqual('{\"message\":\"Hello world!\"}');
69+
70+
const resource2 = preloadData(client, url, {}, {}, { raw: true });
71+
72+
expect(resource2.read()).toStrictEqual(resource.read());
73+
}
74+
});
75+
76+
it('Should not store data to cache when fetchPolicy is network-only', async () => {
77+
fetchMock.mockResponse(JSON.stringify({ message: 'Hello world!' }));
78+
const fabricatedError = new Error('Fabricated error');
79+
const client = createDataClient({ ssr: false });
80+
const url = 'http://localhost:3000/some-rest-api-raw/4';
81+
82+
const resource = preloadData(client, url, {}, {}, {
83+
fetchPolicy: 'network-only',
84+
raw: true,
85+
});
86+
87+
expect(resource.read).toThrow();
88+
89+
fetchMock.mockReject(fabricatedError);
90+
91+
try {
92+
resource.read();
93+
} catch (promise) {
94+
await promise;
95+
96+
expect(resource.read()).toStrictEqual('{\"message\":\"Hello world!\"}');
97+
98+
const resource2 = preloadData(client, url);
99+
100+
expect(resource2.read).toThrowError();
101+
}
102+
});
103+
});

packages/react-isomorphic-data/src/common/__tests__/preloadData.test.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,14 @@ describe('preloadData tests', () => {
3232
});
3333

3434
it('Should throw an error the fetch is rejected', async () => {
35-
const fabricatedError = new Error('Fabricated error');
35+
fetchMock.mockReject(() => Promise.reject('Fabricated error'));
3636
const client = createDataClient({ ssr: false });
3737
const url = 'http://localhost:3000/some-rest-api/2';
3838

3939
const resource = preloadData(client, url);
4040

4141
expect(resource.read).toThrow();
4242

43-
fetchMock.mockReject(fabricatedError);
44-
4543
try {
4644
resource.read();
4745
} catch (promise) {

packages/react-isomorphic-data/src/common/preloadData.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import qsify from '../utils/querystringify/querystringify.js';
77
* Simple cache for preloaded data
88
*/
99
const preloadCache: Record<string, any> = {};
10+
const preloadJsonCache: Record<string, any> = {};
1011

1112
/**
1213
* An imperative API to preload data to implement render-as-you-fetch pattern
@@ -28,12 +29,12 @@ const preloadData = (
2829

2930
const promise = !data
3031
? fetch(fullUrl, finalFetchOpts)
31-
.then((result) => result.json())
32-
.then((json) => {
33-
data = json;
32+
.then((result) => result.text())
33+
.then((resText) => {
34+
data = resText;
3435

3536
if (fetchPolicy !== 'network-only') {
36-
preloadCache[fullUrl] = data;
37+
preloadCache[fullUrl] = resText;
3738
}
3839

3940
fulfilled = true;
@@ -53,7 +54,15 @@ const preloadData = (
5354
// let a SuspenseBoundary catch this
5455
throw promise;
5556
} else {
56-
return data;
57+
if (dataOpts.raw) {
58+
return data;
59+
} else {
60+
if (!preloadJsonCache[data]) {
61+
preloadJsonCache[data] = JSON.parse(data);
62+
}
63+
64+
return preloadJsonCache[data];
65+
}
5766
}
5867
};
5968

packages/react-isomorphic-data/src/common/utils/addToCache.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ const addToCache = (cache: Record<string, any>, url: string, data: Record<string
1313
temp = temp[k];
1414
} else {
1515
// add the data
16-
temp[k] = data;
16+
temp[k] = temp[k] || {};
17+
temp[k].__raw = data;
1718
}
1819
});
1920

0 commit comments

Comments
 (0)