-
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
Fix refetching and pagination when using a custom nodeInterfaceIdField #4053
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall this looks good, some small nits. Could you please create at least one unit test (see existing useRefetchableFragment unit tests) to ensure that this feature doesn't break? Thanks!
fragmentNode: ReaderFragment, | ||
componentDisplayName: string, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: pass identifierField
instead of recomputing it. i would also default to id
if identifierField is null/undefined
const id = | ||
identifierField !== null && | ||
identifierField !== undefined && | ||
identifierField !== '' | ||
? memoRefetchVariables?.[identifierField] | ||
: null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see above, then you could remove the null/undefined checks here. also note that we generally use x == null
to check for both null or undefined (this is the one place where it's reasonable to use double equals).
Thanks for the review @josephsavona! Unfortunately, after running the existing tests, I realized that my changes break some existing functionality related to refetching or paginating Specifically, when paginating or refetching a This is unlike the compiler's behavior when setting the I think we can go forward in three different directions:
IMO, option 1 is very pragmatic, but both 1 and 2 are a bit leaky since they force the hooks to be aware of the compiler's quirks. Option 3 sounds more correct to me. However, adopting it would be a breaking change since some users of What do you think? |
It should already be available on the refetch metadata (?). But approach #2 seems reasonable generally, the compiler can emit metadata in the AST to be consumed at runtime. |
5702c6e
to
f6c69f0
Compare
@josephsavona The scope of this PR increased a bit. Based on your comments, I decided to add another field to the refetchable fragments' metadata, since as per my previous comments, the The new metadata field is pretty explicit so the hooks don't need to have any complex logic to figure it out. It's named I know you asked for a test in the hooks to make sure this doesn't break, but unfortunately that's not straightforward. Currently all the GraphQL operations for the tests are being compiled by the relay-compiler as configured in This makes it impossible to create a new test suite whose GraphQL ops are compiled with a different compiler setting, which is what we would need to test with the There are ways around that but all of them would have a pretty heavy footprint on how the codebase is structured. (For example, one solution could be to exclude the generated artifacts from source control, and generate them on demand before the tests are run. This would allow us to compile and run the test suite as normal, compile it again with the I think that with this change, adding a test to the hooks becomes less important since now the compiler is responsible for explicitly letting the hooks know what the variable should be named. The hooks should just use the provided metadata field, so there isn't any special logic it has to perform in the case when If you insist on having a new test for the hooks, I would require instruction on how exactly to make that happen (given the need of a separate compiler config as I explained above.) |
@josephsavona Have you had a chance to look at my latest changes? |
Any chance of getting this or something similar merged @josephsavona? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall this looks good, and thanks for working on it! I left a few comments. Most are trivial, but there is one about allowing the variable name to be configured independently of the field name. Curious to hear your thoughts.
Apologies for the slow response on this PR.
identifierField != null && | ||
!providedRefetchVariables.hasOwnProperty('id') | ||
identifierQueryVariableName != null && | ||
!providedRefetchVariables.hasOwnProperty(identifierQueryVariableName) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my comment on useRefetchableFragmentInternal_REACT_CACHE.js
above. I think this should probably include an invariant?
@@ -448,8 +455,8 @@ function useRefetchFunction<TQuery: OperationType>( | |||
// If the query needs an identifier value ('id' or similar) and one | |||
// was not explicitly provided, read it from the fragment data. | |||
if ( | |||
identifierField != null && | |||
!providedRefetchVariables.hasOwnProperty('id') | |||
identifierQueryVariableName != null && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. So previously we used the existence of identifierField
to tell us if we are in an operation that needs an identifier. Now we use identifierQueryVariableName
and the use the warning below to ensure we also have an identifierField
?
If there's a compiler invariant that we always have an identifierField
when we have an identifierQueryVariableName
, maybe we should at least add an invariant
call inside this if statement`. For bonus points we could restructure the compiler output to be an optional object that contains both values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, not quite. The warning below was already there before this PR. It was necessary because it doesn't only check for the existence of identifierField
, rather, it makes sure we have an identifierValue
, which is defined as fragmentData[identifierField]
.
Even if the compiler invariant holds and we have a identifierField
whenever there is an identifierQueryVariableName
, identifierValue
might be missing for a number of different reasons outside the control of the compiler. For example, there might be a malfunction in the Relay runtime or perhaps even in the users's GraphQL server.
Nevertheless, if we hit the warning we're definitely in a corrupted state and there's a case to be made for triggering an exception via an invariant, but I would you to confirm that this is indeed what we want since I'm not familiar with the team's heuristics for when to use a warning and when to go for an invariant.
Changing the compiler output as you're describing would still be nice. I'll see if I can get around to doing it.
@@ -196,7 +200,7 @@ function useLoadMoreFunction<TQuery: OperationType>( | |||
|
|||
// If the query needs an identifier value ('id' or similar) and one | |||
// was not explicitly provided, read it from the fragment data. | |||
if (identifierField != null) { | |||
if (identifierQueryVariableName != null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my comment on useRefetchableFragmentInternal_REACT_CACHE.js
above. I think this should probably include an invariant?
@@ -436,6 +436,13 @@ impl<'schema, 'builder, 'config> CodegenBuilder<'schema, 'builder, 'config> { | |||
}); | |||
} | |||
|
|||
if let Some(x) = refetch_metadata.identifier_query_variable_name { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Can we have a more descriptive variable name than x
?
@@ -120,6 +120,7 @@ fn build_refetch_operation( | |||
operation_name: query_name, | |||
path: vec![CONSTANTS.node_field_name], | |||
identifier_field: Some(id_name), | |||
identifier_query_variable_name: Some(id_name), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here we assume the variable name will always match the field name. I've seen cases where people would like to deviate from that assumption. What do you think about adding a schema config option parallel to node_interface_id_field
called node_interface_id_variable_name
or similar?
That would also be useful for the case where someone was depending upon the existing behavior.
Thanks for the review! I added a comment about the Regarding configuring the variable name, I agree it would be a useful feature. I'll add it to the PR and let you know when it's ready for another review! |
any news on this PR? 👍 |
I've been a bit busy lately, but this week I started implementing the changes that Jordan requested. They'll be ready soon! |
f6c69f0
to
49e0059
Compare
6750d06
to
9f3f74d
Compare
9f3f74d
to
c3866a7
Compare
Hey @captbaritone! I finally got around to implementing the changes you requested. Please take a look at the last three commits. The first two of them add support for configuring the variable name, as you suggested. (The default keeps the current beahvior.) |
packages/relay-runtime/yarn.lock
Outdated
@@ -0,0 +1,111 @@ | |||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this got checked in by accident.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed.
packages/react-relay/yarn.lock
Outdated
@@ -0,0 +1,125 @@ | |||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this got checked in by accident.
identifierInfo.identifierField == null || | ||
typeof identifierInfo.identifierField === 'string', | ||
'Relay: getRefetchMetadata(): Expected `identifierField` to be a string.', | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add a similar invariant for identifierQueryVariableName
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, it doesn't hurt and it's consistent with the rest of the function. Added!
@@ -201,7 +207,7 @@ function useLoadMoreFunction<TVariables: Variables>( | |||
|
|||
// If the query needs an identifier value ('id' or similar) and one | |||
// was not explicitly provided, read it from the fragment data. | |||
if (identifierField != null) { | |||
if (identifierInfo != null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused by the relationship between identifierField
and identifierInfo
in this scope. My sense right now is that they are actually both coming from the same metadata but that identifierInfo
is extracted locally and identifierField
is extracted by the parent function and passed in. Is that correct?
If not, maybe we could add a comment explaining the difference between the two? If not, maybe we could get rid of one of them and either always pass in a full identifierInfo
object, or avoid the need for parent functions to pass it in, and instead always extract it locally?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, having both was redundant. I just changed it so that this function doesn't need parent functions to pass identifierInfo
. Now we just extract it locally.
Thanks for your continued work on this @Emilios1995! Left another round of comments. |
Co-authored-by: Jordan Eldredge <jordan@jordaneldredge.com>
Thank you @captbaritone! I just pushed some changes addressing your comments. |
@captbaritone Hey! Any chance we could get this merged? |
@captbaritone has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator. |
Quick update here. I've managed to rebase and adapt this PR to work internally. It's now up for review internally. |
@captbaritone merged this pull request in 0fce632. |
This has landed. Folks should be able to try it out using the @main tag of the NPM packages. Note that you'll need to upgrade both the |
Thank you, @captbaritone! |
Thank @Emilios1995 for this! Works great. |
One thing I had to figure out was adding |
It's worth noting at present it appears the default implementation of
|
@Lalitha-Iyer That's a good point. We did document the new variable here but I can see how it would be better to have a comprehensive guide documenting all the customization options. Such a guide would also be a good place to remind people they might need to customize the @captbaritone Would you like there to be a new Guide on relay.dev called "Using a different field for global IDs" or something similar documenting everything? |
This fixes #3897.
I wasn't able to get the tests to pass on my machine, even onmain
, so I didn't consider whether to add tests for this change.Please let me know whether I should do something other than
yarn install
,yarn build
andyarn test
for them to work.Still, I verified that the changes fix my issue by testing on a project that uses the custom id field. I tested both: Refetching, and loading more data on a pagination fragment.