-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
[relay-compiler] Fix type generation of nullable types in TypeScript #4380
Conversation
Thanks!! My guess is that this is going to be potentially difficult for folks to upgrade to. To help support their migration, would you add a compiler feature flag that gates this behavior, with the correct behavior being the default? We can plan to have at least one release where this change is opt-in before we remove the flag. |
@captbaritone does it has to be a feature flag or can the switch also be on the TypegenConfig? This is already being passed to the TypescriptPrinter, so the change would be less disruptive to make. |
Yeah, that's fine. Could you also do a writeup about this change that we can include in the next release notes? Ideally something that covers:
|
Where should I do the writeup? |
7e6f26f
to
99f5f45
Compare
99f5f45
to
846187d
Compare
Wish I had a better answer to this. For now, maybe just a comment here on the PR or in a linked Google Doc (or similar) |
@captbaritone Sorry for taking some time to get back to this - I was away for a few days. While looking over the code again, I noticed some other type generation issues regarding client schema extensions:
I tried to tackle 1, but I lack the understanding of what |
@captbaritone has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator. |
@captbaritone merged this pull request in cc47bc9. |
@tobias-tengler @captbaritone I was trying to upgrade a project of mine to Relay 15 and came across this addition. I'm not quite ready to tackle this change and update all my code, so I attempted to use the In the config.rs file the Is this just an oversight and theres something I'm missing or do we need to update the |
@ernieturner Thanks for bringing this up! This was indeed a small oversight on my part 😅 Should be fixed with #4482 |
I don't fully understand the purpose of this change. Unless I am missing something, this will just result in every reference to a nullable field either changing to Just want to make sure I understand the rationale for this or if I am even correctly thinking about how to adopt this change. At the moment, it is just a bunch of changes like: const suggestions = (
<OneLinerSuggestions
- accountRef={userAccountRef}
+ accountRef={userAccountRef ?? null}
isOpen={isOpen}
onClose={closePopover}
onEdit={() => {
onEdit();
closePopover();
}}
onSelect={handleSelectSuggestion}
title="One-Liner Suggestions"
/>
); export const isUpfrontTransfer = (
transaction: SupportedTransactionRelayData,
): transaction is UpFrontTransfer => {
return (
'upfrontPayment' in transaction &&
transaction.upfrontPayment !== null &&
+ transaction.upfrontPayment !== undefined &&
transaction.upfrontPayment.amountCents > 0 &&
(transaction.__typename === 'ContraTransfer' ||
transaction.__typename === 'StripeTransferV2')
);
}; );
if (profile === null) return null;
+ if (profile === undefined) return null;
return (
<>
<Helmet>
<title>{`Opportunities offered by ${profile.firstName} ${profile.lastName}`}</title>
</Helmet> |
@gajus The reason I created this PR was this comment by one of the maintainers: #4370 (comment) You don't have to duplicate all your if conditionals, just use I think an example of where this change could help you catch bugs, is someone deleting a record from the store. This could result in your linked fields or arrays suddently containing an In theory this could also happen for non-nullable types, so in hindsight this should've maybe been solved in the store and not the type generation. Maybe @captbaritone can provide some additional insights. |
That's inconvenient because we enforce strict checks across our codebase. So it is going to be either: );
if (profile === null) return null;
+ if (profile === undefined) return null;
return (
<>
<Helmet>
<title>{`Opportunities offered by ${profile.firstName} ${profile.lastName}`}</title>
</Helmet> or );
- if (profile === null) return null;
+ // eslint-disable-next-line no-eq-null, eqeqeq
+ if (profile == null) return null;
return (
<>
<Helmet>
<title>{`Opportunities offered by ${profile.firstName} ${profile.lastName}`}</title>
</Helmet> Neither of which is a desirable change. This also means patching thousands of references in codebase, which is a major undertaking. We can probably write a script to automate most of it based on TypeScript suggestions, but my problem is that it makes the code less readable, i.e. goes back to this being undesirable change from maintenance perspective.
@captbaritone This really does feel like it should have been solved in the store and not the type generation. |
@captbaritone any chance of reconsidering this? |
@kassens @alunyov @SamChou19815 is it possible to get some follow up here? |
So, I think the rationale for this change is well described by @tobias-tengler - in Relay, null and undefined are different things. If the type system can help reflect the actual runtime behavior, it is a good change from the correctness perspective. However, from a practical point of view, I agree that this change may break a lot of existing codebases, where, for various reasons, the handling of null/undefined values may be different. We also had to disable this flag for one of our internal projects that was using TypeScript. It is likely we won’t be updating that project to a different null handling in the near future, so the feature flag will probably stay. @gajus, am I understanding correctly that your proposal is to revert this change (type updates) and handle this at runtime by always returning null values and never undefined? |
That's correct. I'd propose to revert this. There is extremely little value for this to be exposed to the client. You could make it exposed behind a feature flag (the inverse of the current behavior), but even then it is very hard to see how this would benefit someone even if they are starting the project from the ground up. |
I think there may be some confusion. The change here makes the type script generated types match the long-standing behavior of the runtime (and the generated Flow types). undefined is (and has been) a possible value in these locations forever. The types are just being updated to reflect this truth. This change does not introduce new undefined values, but just corrects the types to correctly represent the fact that these values can, in fact, be undefined. |
I see. In which case, I would want to have an option to just return We are currently working on a wrapper to do this for us, but I think that keeping the current behavior as the default will severely cripple new user adoption of Relay. |
Doesn't the |
Given this discussion it seems like it's reasonable to reverse course on the plan to remove the feature flag and just keep it around forever. I can't speak to the feasibility of that, but it seems like something that people should be allowed to opt into forever if they want. Regarding the use case where you're still getting |
Interesting. Did version 16 return only null there? If so, can you open a separate issue? |
I'm open to leaving the feature flag indefinitely. We are starting to look at alternate ways to handle missing data more explicitly, perhaps piggybacking on our explicit error handling efforts. So, there's a possibility that we'll have a fundamental solution which can allow us to avoid returning |
I'm not particularly concerned about the current default interfering with new adoptions of Relay. Handling both null and undefined as a single concept with |
I've updated the release notes to clarify our thinking here: https://github.com/facebook/relay/releases/tag/v16.0.0 |
I'm not sure if this issue got opened, but I realized during some upgrades that |
To keep the old type generation behavior with \ `typescriptExcludeUndefinedFromNullableUnion` for now. See: facebook/relay#4380 Signed-off-by: Sergey Zakhlypa <[email protected]>
Closes: #4379
Release Notes
Previously, the relay-compiler would generate a union of the actual type and
null
(T | null
) for nullable fields when targeting TypeScript. This was not correct, since the Relay store can also returnundefined
in the case of missing data. This version now produces a union of the actual type,null
andundefined
(T | null | undefined
).Consequently you now also have to check both the
null
andundefined
case, before accessing a field's value:Since this is a pretty big change, we're offering the
typescriptExcludeUndefinedFromNullableUnion
feature flag in therelay-compiler
config to keep the old type generation behavior for now:Just be aware that we will remove this feature flag in a future release and that you might run into an unexpected
undefined
, if you're not checking forundefined
before accessing a field - especially if it's a client-only field.