relay icon indicating copy to clipboard operation
relay copied to clipboard

Connection Fields in Query are parsed to `undefined` even though network response has data in those fields

Open KieranTH opened this issue 1 year ago • 5 comments

Hooks used

  • useLazyLoadQuery
  • usePaginationFragment

Versions

  • React: 19.0.0-rc-66855b96-20241106
  • React-Relay: 18.2.0

Issue Description

When using loadNext from the usePaginationFragment hook, connection fields are parsed as undefined - But in the network response exist correctly.

Any data fetched initially is correctly structured via the Server response - this only happens with PAGINATED data.

This seems to be a regression after updating to the latest version of Relay. This setup was working previously.

I've been able to narrow down one of the triggers for it.

If i have a connection like friends(), if i add a VARIABLE of first - the bug appears.

Error: friends(first: $friendsFirst)

Works: friends(first: 20)

Example of Data

Retrieved from Server:

{
  "id": "123",
  "friends": { "edges": [] }
}

Returned from usePaginationFragment:

{
  "id": "123",
  "friends": undefined
}

Example of Query

Wrapper Query:

query HomeQuery($first: Int = 20, $cursor: Cursor, $where: WhereInput = {}, ...bunchOfArgs){
   ...HomeFragment @arguments(first: $first, cursor: $cursor, where: $where, ...bunchOfArgs: $...bunchOfArgs)
 }

Fragment:

fragment HomeFragment on Query
  @argumentDefinitions(
    first: { type: "Int", defaultValue: 20 }
    cursor: { type: "Cursor" }
    where: { type: "WhereInput", defaultValue: {} }
    ...bunchOfArgsDefs
  )
  @refetchable(queryName: "HomePaginationQuery") {
    objects(first: $first, after: $cursor, where: $where) 
    @connection(key: "HomeConnection_objects", filters: ["where"]){
      edges {
        node {
          id
          friends(first: 20 # Changing this to a variable causes the bug to appear){
            edges {
               node {
                  id
               }
            }
          }
        }
      }
    }
  }

KieranTH avatar Jan 09 '25 14:01 KieranTH

@KieranTH do you have the full response payload to share for the loadNext that parsed to undefined? Including

{
    data: {}, 
    errors: [] // (if any errors exist)
} 

itamark avatar Jan 09 '25 14:01 itamark

@KieranTH do you have the full response payload to share for the loadNext that parsed to undefined? Including

{
    data: {}, 
    errors: [] // (if any errors exist)
} 

There are no errors in response by the backend.

The response looks something like this:

{
 data: {
   objects: {
     edges: [
       {
          node: {
             "id": "123",
             "friends": { "edges": [] }
          }
       }
     ]
   }
 }
}

I've actually been able to narrow it down fully.

When a default value isn't given to the first variable IN THE WRAPPER QUERY, the bug appears.

BROKEN:

HomeQuery($first: Int = 20, $cursor: Cursor, $where: WhereInput = {}, $friendFrist: Int)

WORKING:

HomeQuery($first: Int = 20, $cursor: Cursor, $where: WhereInput = {}, $friendFirst: Int = 20)

Even though I have a default set in the Fragment Arguments:

{
friendFirst: {type: "Int", defaultValue: 20}
}

Any ideas why this could cause such weird behaviour?

KieranTH avatar Jan 09 '25 15:01 KieranTH

Interesting. Thanks for the report. So, if I understand correctly, the bug triggers when:

  1. The first argument to your connection field is provided by a fragment variable with a default value provided (first: { type: "Int", defaultValue: 20 }
  2. You are passing the fragment to the query using a query variable that does not have a default value
  3. You make an initial query (With what variables passed?)
  4. You paginate (with no variables passed?)

If all these things are true, you end up with "friends": undefined in your component?

I'm a little confused by the inclusion of $first and $firstFriend and how they actually fit together in the real example.

Cache keys in the store are computed based on a field's name as well as a serialized form of the variables passed to that field. I suspect there's some disagreement between one or more of:

  1. RelayResponseNormalizer (which uses the generated artifact for the query)
  2. Connection handler
  3. RelayReader (which uses the generated artifact for the fragment).

Perhaps somehow this combination of variables/defaults triggers a bug where they end up computing different cache keys? A snapshot of what's in the store before/after you issue the pagination query might help us understand what's gone wrong here.

The next step here would be to create a reproduction of the issue as a test. Probably following the examples given here: https://github.com/facebook/relay/blob/main/packages/react-relay/relay-hooks/tests/usePaginationFragment-test.js

captbaritone avatar Jan 22 '25 18:01 captbaritone

I'm facing this issue as well in react-relay 19 where non-deterministically a node off of which I'm fetching a connection is undefined. It doesn't happen consistantly. Happy to provide any details.

mikeldking avatar Jul 31 '25 04:07 mikeldking

@mikeldking Are you using React Activity with relay hook ? I also meet same issue during using activity.

so I also try to figure out that root cause.

kkak10 avatar Dec 16 '25 13:12 kkak10