Skip to content

Commit be353d2

Browse files
authored
[Flight Reply] Add undefined and Iterable Support (#26365)
These were recently added to ReactClientValue and so needs to be reflected in ReactServerValue too.
1 parent ef8bdbe commit be353d2

File tree

3 files changed

+100
-5
lines changed

3 files changed

+100
-5
lines changed

packages/react-client/src/ReactFlightReplyClient.js

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
REACT_ELEMENT_TYPE,
1616
REACT_LAZY_TYPE,
1717
REACT_PROVIDER_TYPE,
18+
getIteratorFn,
1819
} from 'shared/ReactSymbols';
1920

2021
import {
@@ -46,6 +47,7 @@ export type ReactServerValue =
4647
| number
4748
| symbol
4849
| null
50+
| void
4951
| Iterable<ReactServerValue>
5052
| Array<ReactServerValue>
5153
| ReactServerObject
@@ -69,6 +71,10 @@ function serializeSymbolReference(name: string): string {
6971
return '$S' + name;
7072
}
7173

74+
function serializeUndefined(): string {
75+
return '$undefined';
76+
}
77+
7278
function escapeStringValue(value: string): string {
7379
if (value[0] === '$') {
7480
// We need to escape $ prefixed strings since we use those to encode
@@ -154,6 +160,12 @@ export function processReply(
154160
);
155161
return serializePromiseID(promiseId);
156162
}
163+
if (!isArray(value)) {
164+
const iteratorFn = getIteratorFn(value);
165+
if (iteratorFn) {
166+
return Array.from((value: any));
167+
}
168+
}
157169

158170
if (__DEV__) {
159171
if (value !== null && !isArray(value)) {
@@ -208,14 +220,14 @@ export function processReply(
208220
return escapeStringValue(value);
209221
}
210222

211-
if (
212-
typeof value === 'boolean' ||
213-
typeof value === 'number' ||
214-
typeof value === 'undefined'
215-
) {
223+
if (typeof value === 'boolean' || typeof value === 'number') {
216224
return value;
217225
}
218226

227+
if (typeof value === 'undefined') {
228+
return serializeUndefined();
229+
}
230+
219231
if (typeof value === 'function') {
220232
const metaData = knownServerReferences.get(value);
221233
if (metaData !== undefined) {
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @emails react-core
8+
*/
9+
10+
'use strict';
11+
12+
// Polyfills for test environment
13+
global.ReadableStream =
14+
require('web-streams-polyfill/ponyfill/es6').ReadableStream;
15+
global.TextEncoder = require('util').TextEncoder;
16+
global.TextDecoder = require('util').TextDecoder;
17+
18+
// let serverExports;
19+
let webpackServerMap;
20+
let ReactServerDOMServer;
21+
let ReactServerDOMClient;
22+
23+
describe('ReactFlightDOMReply', () => {
24+
beforeEach(() => {
25+
jest.resetModules();
26+
const WebpackMock = require('./utils/WebpackMock');
27+
// serverExports = WebpackMock.serverExports;
28+
webpackServerMap = WebpackMock.webpackServerMap;
29+
ReactServerDOMServer = require('react-server-dom-webpack/server.browser');
30+
ReactServerDOMClient = require('react-server-dom-webpack/client');
31+
});
32+
33+
it('can pass undefined as a reply', async () => {
34+
const body = await ReactServerDOMClient.encodeReply(undefined);
35+
const missing = await ReactServerDOMServer.decodeReply(
36+
body,
37+
webpackServerMap,
38+
);
39+
expect(missing).toBe(undefined);
40+
41+
const body2 = await ReactServerDOMClient.encodeReply({
42+
array: [undefined, null, undefined],
43+
prop: undefined,
44+
});
45+
const object = await ReactServerDOMServer.decodeReply(
46+
body2,
47+
webpackServerMap,
48+
);
49+
expect(object.array.length).toBe(3);
50+
expect(object.array[0]).toBe(undefined);
51+
expect(object.array[1]).toBe(null);
52+
expect(object.array[3]).toBe(undefined);
53+
expect(object.prop).toBe(undefined);
54+
// These should really be true but our deserialization doesn't currently deal with it.
55+
expect('3' in object.array).toBe(false);
56+
expect('prop' in object).toBe(false);
57+
});
58+
59+
it('can pass an iterable as a reply', async () => {
60+
const body = await ReactServerDOMClient.encodeReply({
61+
[Symbol.iterator]: function* () {
62+
yield 'A';
63+
yield 'B';
64+
yield 'C';
65+
},
66+
});
67+
const iterable = await ReactServerDOMServer.decodeReply(
68+
body,
69+
webpackServerMap,
70+
);
71+
const items = [];
72+
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
73+
for (const item of iterable) {
74+
items.push(item);
75+
}
76+
expect(items).toEqual(['A', 'B', 'C']);
77+
});
78+
});

packages/react-server/src/ReactFlightReplyServer.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,11 @@ function parseModelString(
397397
key,
398398
);
399399
}
400+
case 'u': {
401+
// matches "$undefined"
402+
// Special encoding for `undefined` which can't be serialized as JSON otherwise.
403+
return undefined;
404+
}
400405
default: {
401406
// We assume that anything else is a reference ID.
402407
const id = parseInt(value.substring(1), 16);

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy