Fix refetching and pagination when using a custom nodeInterfaceIdField#4053
Fix refetching and pagination when using a custom nodeInterfaceIdField#4053Emilios1995 wants to merge 13 commits intofacebook:mainfrom
Conversation
josephsavona
left a comment
There was a problem hiding this comment.
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.
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.
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? |
captbaritone
left a comment
There was a problem hiding this comment.
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.
See my comment on useRefetchableFragmentInternal_REACT_CACHE.js above. I think this should probably include an invariant?
| if ( | ||
| identifierField != null && | ||
| !providedRefetchVariables.hasOwnProperty('id') | ||
| identifierQueryVariableName != null && |
There was a problem hiding this comment.
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.
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.
| // 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.
See my comment on useRefetchableFragmentInternal_REACT_CACHE.js above. I think this should probably include an invariant?
| }); | ||
| } | ||
|
|
||
| if let Some(x) = refetch_metadata.identifier_query_variable_name { |
There was a problem hiding this comment.
Nit: Can we have a more descriptive variable name than x?
| 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.
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.
Looks like this got checked in by accident.
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.
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.
Should we add a similar invariant for identifierQueryVariableName?
There was a problem hiding this comment.
Sure, it doesn't hurt and it's consistent with the rest of the function. Added!
| // 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.
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.
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 buildandyarn testfor 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.