-
Notifications
You must be signed in to change notification settings - Fork 47.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bug: undefined
within props is not faithfully deserialized from RSC payload
#27872
Comments
This seems to stem directly from the behavior of > function reviver(key, val) { return val === '$undefined') ? undefined : val }
> JSON.parse(`{ "x": "$undefined" }`, reviver)
{}
> JSON.parse(`[0, "$undefined", 2]`,reviver)
[ 0, <1 empty item>, 2 ] One way i've found to work around the array part is to do something like this in the reviver: function reviver(key, val) {
if (Array.isArray(val)) { return [...val] }
...
} Empty slots don't affect iteration, so this turns |
This is a known issue and basically just worth the tradeoff for perf. |
We could maybe check if Note that we also don't model empty slots separately from this concept so if we did, maybe we would need to also model empty slots separately. |
Made a little benchmark comparing a couple of these (though the inputs might not be very representative) Interestingly enough, in Firefox, a fixup queue that sets either way, it might not be a big deal, perf-wise? feels like in a decently-sized tree with few |
The other issue is that since we don't emit the property from an object that has a value of undefined, for the same reason, we're still not really consistent about this. |
well, in the queue implementation, you kinda get both for free just by doing function parseWithFixup(input) {
const fixups = [];
function reviver(key, val) {
if (val === "$undefined") {
// we'll replace this value with an undefined at the end.
fixups.push([this, key]);
if (key === "") {
// we might be at the top-level, i.e. visiting the root.
// in case the whole string is "$undefined", we return undefined here --
// we won't get the chance to replace it after.
return undefined;
}
return null; // we'll replace it later, but just in case... it's close enough
}
return val;
};
const res = JSON.parse(input, reviver);
for (let i = 0, len = fixups.length; i < len; i++) {
const fixup = fixups[i];
const target = fixup[0], key = fixup[1];
target[key] = undefined;
}
return res;
} > parseWithFixup(`[1, "$undefined", 2]`)
[ 1, undefined, 2 ]
> parseWithFixup(`{ "x": "$undefined" }`)
{ x: undefined }
> parseWithFixup(`"$undefined"`)
undefined |
This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment! |
A failing test is already added in react/packages/react-client/src/__tests__/ReactFlight-test.js Lines 297 to 328 in e9ae2c8
I played with different fixes in the past but they all have different tradeoffs. There's one version that just uses the same queue method @lubieowoce proposed but also one that has constant space usage but more runtime checks. Another argument for biting the perf bullet here is that in TypeScript you'd use the |
React version:
18.3.0-canary-0cdfef19b-20231211
Related issues: #25687
Steps To Reproduce
Object variant:
prop={{ x: undefined }}
from a server component to a client componentArray variant:
prop={[0, undefined, 2]}
from a server component to a client componentLink to code example:
https://codesandbox.io/p/devbox/compassionate-benz-rj9dct
The current behavior
Object properties that are
undefined
aren't present in the deserialized object, and arrays end up with empty slots instead:The expected behavior
If a property or array item is
undefined
, it should be deserialized as such. Object keys should remain present (with a value ofundefined
), and arrays shouldn't have empty slots.Object properties aren't that big of a deal, although a key being present (but undefined) may have some semantic meaning in the user's data model, so it's still not ideal. But empty slots behave in very surprising ways with e.g.
.map()
:this is likely to bite anyone who maps over an array in order to display something, which is common in react.
The text was updated successfully, but these errors were encountered: