diff --git a/packages/react/src/ReactFetch.js b/packages/react/src/ReactFetch.js index 7ceea672acd45..2a5637efc0d60 100644 --- a/packages/react/src/ReactFetch.js +++ b/packages/react/src/ReactFetch.js @@ -74,7 +74,13 @@ if (enableCache && enableFetchInstrumentation) { url = resource; } else { // Normalize the request. - const request = new Request(resource, options); + // if resource is not a string or a URL (its an instance of Request) + // then do not instantiate a new Request but instead + // reuse the request as to not disturb the body in the event it's a ReadableStream. + const request = + typeof resource === 'string' || resource instanceof URL + ? new Request(resource, options) + : resource; if ( (request.method !== 'GET' && request.method !== 'HEAD') || // $FlowFixMe[prop-missing]: keepalive is real diff --git a/packages/react/src/__tests__/ReactFetch-test.js b/packages/react/src/__tests__/ReactFetch-test.js index b3f8843b8856e..9558ead7c1bc4 100644 --- a/packages/react/src/__tests__/ReactFetch-test.js +++ b/packages/react/src/__tests__/ReactFetch-test.js @@ -135,6 +135,22 @@ describe('ReactFetch', () => { expect(fetchCount).toBe(1); }); + // @gate enableFetchInstrumentation && enableCache + it('can dedupe fetches using URL and not', async () => { + const url = 'http://example.com/'; + function Component() { + const response = use(fetch(url)); + const text = use(response.text()); + const response2 = use(fetch(new URL(url))); + const text2 = use(response2.text()); + return text + ' ' + text2; + } + expect(await render(Component)).toMatchInlineSnapshot( + `"GET ${url} [] GET ${url} []"`, + ); + expect(fetchCount).toBe(1); + }); + it('can opt-out of deduping fetches inside of render with custom signal', async () => { const controller = new AbortController(); function useCustomHook() {