Skip to content

Commit dd45307

Browse files
eps1lonunstubbable
andauthored
[FlightReply] Type hardening and performance improvements (facebook#36425)
Co-authored-by: Hendrik Liebau <mail@hendrik-liebau.de>
1 parent 9635257 commit dd45307

6 files changed

Lines changed: 214 additions & 42 deletions

File tree

.eslintrc.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,8 @@ module.exports = {
582582
CopyInspectedElementPath: 'readonly',
583583
DOMHighResTimeStamp: 'readonly',
584584
EventListener: 'readonly',
585+
// Flow type
586+
FormDataEntryValue: 'readonly',
585587
Iterable: 'readonly',
586588
AsyncIterable: 'readonly',
587589
$AsyncIterable: 'readonly',

flow-typed/environments/bom.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -682,11 +682,11 @@ declare class FormData {
682682
get(name: string): ?FormDataEntryValue;
683683
getAll(name: string): Array<FormDataEntryValue>;
684684

685-
set(name: string, value: string): void;
685+
set(name: string, value: FormDataEntryValue): void;
686686
set(name: string, value: Blob, filename?: string): void;
687687
set(name: string, value: File, filename?: string): void;
688688

689-
append(name: string, value: string): void;
689+
append(name: string, value: FormDataEntryValue): void;
690690
append(name: string, value: Blob, filename?: string): void;
691691
append(name: string, value: File, filename?: string): void;
692692

packages/react-client/src/ReactFlightReplyClient.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,9 @@ export function processReply(
598598
// Copy all the form fields with a prefix for this reference.
599599
// These must come first in the form order because we assume that all the
600600
// fields are available before this is referenced.
601-
const prefix = formFieldPrefix + refId + '_';
601+
// We include a special marker so that the Server can detect FormData entries
602+
// that are values in referenced FormData objects.
603+
const prefix = formFieldPrefix + '_' + refId + '_';
602604
// $FlowFixMe[prop-missing]: FormData has forEach.
603605
value.forEach((originalValue: string | File, originalKey: string) => {
604606
// $FlowFixMe[incompatible-call]
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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+
backingStore.data.append(key, value);
62+
let keys = backingStore.keys;
63+
if (keys === null) {
64+
keys = backingStore.keys = Array.from(backingStore.data.keys());
65+
backingStore.keyPointer = 0;
66+
} else {
67+
keys.push(key);
68+
}
69+
}
70+
71+
export function appendBackingFile(
72+
backingStore: BackingFormData,
73+
key: string,
74+
value: Blob,
75+
filename: string,
76+
): void {
77+
backingStore.data.append(key, value, filename);
78+
let keys = backingStore.keys;
79+
if (keys === null) {
80+
keys = backingStore.keys = Array.from(backingStore.data.keys());
81+
backingStore.keyPointer = 0;
82+
} else {
83+
keys.push(key);
84+
}
85+
}
86+
87+
export function getBackingEntry(
88+
backingStore: BackingFormData,
89+
key: string,
90+
): ?FormDataEntryValue {
91+
return backingStore.data.get(key);
92+
}
93+
94+
export function getAllBackingEntries(
95+
backingStore: BackingFormData,
96+
key: string,
97+
): Array<FormDataEntryValue> {
98+
return backingStore.data.getAll(key);
99+
}
100+
101+
export function createBackingFormData(formData: FormData): BackingFormData {
102+
return {
103+
data: formData,
104+
keyPointer: -1,
105+
keys: null,
106+
};
107+
}

0 commit comments

Comments
 (0)