Skip to content

Commit d015ec7

Browse files
eps1lonunstubbable
andcommitted
[FlightReply] Type hardening and performance improvements
Co-authored-by: Hendrik Liebau <mail@hendrik-liebau.de>
1 parent 1fdab2f commit d015ec7

5 files changed

Lines changed: 217 additions & 41 deletions

File tree

.eslintrc.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,8 @@ module.exports = {
570570
CopyInspectedElementPath: 'readonly',
571571
DOMHighResTimeStamp: 'readonly',
572572
EventListener: 'readonly',
573+
// Flow type
574+
FormDataEntryValue: 'readonly',
573575
Iterable: 'readonly',
574576
AsyncIterable: 'readonly',
575577
$AsyncIterable: 'readonly',
@@ -609,6 +611,7 @@ module.exports = {
609611
TimeoutID: 'readonly',
610612
WheelEventHandler: 'readonly',
611613
FinalizationRegistry: 'readonly',
614+
Exclude: 'readonly',
612615
Omit: 'readonly',
613616
Keyframe: 'readonly',
614617
PropertyIndexedKeyframes: 'readonly',

packages/react-client/src/ReactFlightReplyClient.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -582,7 +582,9 @@ export function processReply(
582582
// Copy all the form fields with a prefix for this reference.
583583
// These must come first in the form order because we assume that all the
584584
// fields are available before this is referenced.
585-
const prefix = formFieldPrefix + refId + '_';
585+
// We include a special marker so that the Server can detect FormData entries
586+
// that are values in referenced FormData objects.
587+
const prefix = formFieldPrefix + '_' + refId + '_';
586588
// $FlowFixMe[prop-missing]: FormData has forEach.
587589
value.forEach((originalValue: string | File, originalKey: string) => {
588590
// $FlowFixMe[incompatible-call]
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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+
* @flow
8+
*/
9+
10+
/**
11+
* Backing FormData is a wrapper around FormData that allows iterating over the
12+
* keys while allowing to evict values from the FormData without affecting the iteration.
13+
* Native FormData.keys() will skip keys if entries with Blob are deleted e.g.
14+
* ```js
15+
* const formData = new FormData();
16+
* formData.append('a', new Blob());
17+
* formData.append('b', 2);
18+
* const keys = formData.keys();
19+
* keys.next().value; // 'a'
20+
* formData.delete('a');
21+
* keys.next().value; // undefined, but we expect 'b'
22+
* ```
23+
*/
24+
export opaque type BackingFormData = {
25+
data: FormData,
26+
keyPointer: number,
27+
// Lazily initialized array of keys. We only need this at the moment
28+
// for referenced FormData.
29+
keys: null | Array<string>,
30+
};
31+
32+
export function peekBackingEntry(backingStore: BackingFormData): string | void {
33+
let keys = backingStore.keys;
34+
if (keys === null) {
35+
keys = backingStore.keys = Array.from(backingStore.data.keys());
36+
backingStore.keyPointer = 0;
37+
}
38+
39+
return keys[backingStore.keyPointer];
40+
}
41+
42+
export function advanceBackingEntryIterator(
43+
backingStore: BackingFormData,
44+
): void {
45+
backingStore.keyPointer++;
46+
}
47+
48+
export function consumeBackingEntry(
49+
backingStore: BackingFormData,
50+
key: string,
51+
): void {
52+
backingStore.data.delete(key);
53+
backingStore.keyPointer++;
54+
}
55+
56+
export function appendBackingEntry(
57+
backingStore: BackingFormData,
58+
key: string,
59+
value: FormDataEntryValue,
60+
): void {
61+
// $FlowFixMe[incompatible-call] Older versions of Flow don't know about the overloaded append() method that accepts FormDataEntryValue without `filename`.
62+
backingStore.data.append(key, value);
63+
let keys = backingStore.keys;
64+
if (keys === null) {
65+
keys = backingStore.keys = Array.from(backingStore.data.keys());
66+
backingStore.keyPointer = 0;
67+
} else {
68+
keys.push(key);
69+
}
70+
}
71+
72+
export function appendBackingFile(
73+
backingStore: BackingFormData,
74+
key: string,
75+
value: Blob,
76+
filename: string,
77+
): void {
78+
backingStore.data.append(key, value, filename);
79+
let keys = backingStore.keys;
80+
if (keys === null) {
81+
keys = backingStore.keys = Array.from(backingStore.data.keys());
82+
backingStore.keyPointer = 0;
83+
} else {
84+
keys.push(key);
85+
}
86+
}
87+
88+
export function getBackingEntry(
89+
backingStore: BackingFormData,
90+
key: string,
91+
): ?FormDataEntryValue {
92+
return backingStore.data.get(key);
93+
}
94+
95+
export function getAllBackingEntries(
96+
backingStore: BackingFormData,
97+
key: string,
98+
): Array<FormDataEntryValue> {
99+
return backingStore.data.getAll(key);
100+
}
101+
102+
export function createBackingFormData(formData: FormData): BackingFormData {
103+
return {
104+
data: formData,
105+
keyPointer: -1,
106+
keys: null,
107+
};
108+
}

0 commit comments

Comments
 (0)