relay icon indicating copy to clipboard operation
relay copied to clipboard

Spread fragment through Connection

Open AdrienLemaire opened this issue 8 years ago • 28 comments

Bug

Invariant Violation: RelayConnectionTransform: Expected field `projects: ProjectNodeConnection` to have a edges selection in document `MenuQuery`

Relay compiler should notice that the fragment will spread with edges in the projects field, and pass without errors.

Code

Query:

  query MenuQuery($unitId: ID!) {
    unit(id: $unitId) {
      id
      projects(first: 10) @connection(key: "Unit_projects") {
        ...MenuProjectList_list
      }
    }
  }

fragment:

  fragment MenuProjectList_list on ProjectNodeConnection {
    edges {
      node {
        ...MenuProjectItem_item
      }
    }
  }

Solution ?

I've tried many variations (trying to set the @connection within the fragment, trying to spread directly in unit instead of projects, etc. Nothing worked.

I use @connection a lot in order to update my lists after mutations. I'm unable to use fragments though, any suggestion to get fragments and @connection to play nicely until this relay-compiler bug gets fixed ? I will bypass my MenuProjectList component and directly call MenuProjectItem from my MenuQuery for now, but it lacks consistency with my other queries without @connection

AdrienLemaire avatar Jul 20 '17 13:07 AdrienLemaire

I've had similar issues. You may want to look at your schema for a solution. I realized that I didn't understand some of the Types involved when fragmenting my queries. Also consider moving your edges/node blocks up into your MenuQuery query. I know visually things look correct but Relay is kinda strange in what it expects to find. While spreading the MenuProjectList in theory should expose edges { node { .... .... to the connection... I've found that it literally expects to see edges and not a child component being spread.

Here is a snippet from my codebase that may or may not be helpful. Renamed some things.

export default createPaginationContainer(
  Table,
  {
    viewer: graphql`
      fragment Table_viewer on Viewer {
        allTables(
          first: $count
          after: $cursor
        ) @connection(key: "Table_allTables") {
          edges {
            node {
              ...Row
            }
          }
          pageInfo {
            endCursor
            hasNextPage
          }
        }
      }
  `,
  },
export default createFragmentContainer(
  Row,
  graphql`
    fragment Row on Table {
      node1
      node2
      node3
      etc....
    }
  `,
);

scotmatson avatar Aug 09 '17 16:08 scotmatson

can we close this?

sibelius avatar Oct 26 '17 12:10 sibelius

I think I need spreading fragment in connection work too. No idea how to reuse pagination component without it. For example, when User and Collection both have posts connection field, a component that handles edges can't be shared in those parent components.

// If you can't spread this fragment in connections, 

fragment PostsPagination_posts on PostConnection {
  edges {
    node {
       id
    }
  }
}

// This code can't be DRYed.

fragment User_posts on User {
   posts(
     first: $count
   ) @connection(key:  'user_posts') {
      edges {
         node {
            id
         }
      }
   }
}

fragment Collection_posts on Collection {
   posts(
     first: $count
   ) @connection(key:  'Collection_posts') {
      edges {
         node {
            id
         }
      }
   }
}

In the case above, PostsPagination component cannot be reused.

taiki-t avatar Nov 04 '17 13:11 taiki-t

https://github.com/facebook/relay/issues/1705#issuecomment-340220976

bharathmuraleedharan avatar Dec 07 '17 11:12 bharathmuraleedharan

This is still a very valid issue especially if you want to use a union to return connection or errors at the same level. See: https://github.com/artsy/artsy.github.io/issues/495#issuecomment-465667460

It is not possible to use this directive even with custom handler @connection(handler: "..."). It's actually even described in the source code: https://github.com/facebook/relay/blob/ae057d84074bc5f0428f0c0f632b53464cebe924/packages/relay-compiler/handlers/connection/RelayConnectionTransform.js#L333-L336

Would you please consider adding a support for it? :)

mrtnzlml avatar Feb 25 '19 19:02 mrtnzlml

What you can do for know is let your connection fragment have the @connection directive

like this:

fragment PostsPagination_posts on PostConnection @connection(key: "PostsPagination_posts", filters: []) {
  edges {
    node {
       id
    }
  }
}

then you can use like this:

posts(first: 100) {
    ...PostsPagination_posts
}

does this approach solves your issue?

sibelius avatar Feb 25 '19 19:02 sibelius

Yes, this is possible. So basically you have to have a root query spreading the fragment where you make the distinction between error and connection:

fragment LocationsPaginatedRefetch_data on DangerZone_RootQuery {
  incrementalPagination: allLocations {
    ... on LocationConnection {
      ...LocationsPaginatedRefetch_test # <<< @connection inside
    }
    ... on DangerZone_HTTPErrorType {
      ...HTTPError_error
    }
  }
}

And later have a fragment to handle the connection:

fragment LocationsPaginatedRefetch_test on LocationConnection
  @connection(key: "allLocations_incrementalPagination") {
  edges {
    node {
      id
      ...Location_location # <<< and optionally another fragment for the UI beauty :)
    }
  }
}

Correcto? Yeah, makes sense but still, it would be nice to be able to do it in one swoop. So let's take it as a feature request... 😊

Example taken from https://github.com/kiwicom/relay-example

mrtnzlml avatar Feb 25 '19 19:02 mrtnzlml

@sibelius Have you tried the approach above? Note that RelayConnectionTransform only supports using @connection on a field, not a fragment definition.

josephsavona avatar Feb 25 '19 23:02 josephsavona

The approach above is working well for us

sibelius avatar Feb 26 '19 00:02 sibelius

Hmm, I would not expect that to work. @connection shouldn't be allowed on fragment definitions, that's a bug.

josephsavona avatar Feb 26 '19 16:02 josephsavona

@josephsavona 😱 Do you have any advice for my problem, please? This was the only sane workaround I know. Others are: change GraphQL schema (I don't want), do not use @connection or add the support into Relay Compiler (which is both not that easy from my point of view).

mrtnzlml avatar Feb 26 '19 17:02 mrtnzlml

You can manually do what the connection transform does:

  • Use @__clientField(handle: "connection") on the connection field to trigger the runtime to derive/update the value w ConnectionHandler, just as if you'd put @connection on the field.
  • Manually fetch the relevant connection fields (pageInfo, edges.cursor, etc).

This is subject to change in upcoming releases so this isn't a great solution if you need to use this pattern widely, but for a one-off it would work while we identify a better solution.

josephsavona avatar Feb 26 '19 18:02 josephsavona

@josephsavona So technically the connection handler works correctly in this situation and it's just disabled in Relay Compiler? I would be afraid to use the @__clientField directive directly but from your comment seems like there should be no problem with this. Thanks! :)

mrtnzlml avatar Feb 26 '19 18:02 mrtnzlml

So technically the connection handler works correctly in this situation and it's just disabled in Relay Compiler

Generally speaking yes, though note that ConnectionHandler does make assumptions about the shape of the response field. You would probably have to build your own wrapper around ConnectionHandler to deal with the fact that your connection returns a Success/Error union.

josephsavona avatar Feb 26 '19 18:02 josephsavona

@mrtnzlml Please share what you come up with! 🙏

alloy avatar Mar 04 '19 11:03 alloy

2 sibelius

does this approach solves your issue?

in relay-compiler version 5.0.0 (and 6 as well) this is an error: Invalid directives @connection found on FRAGMENT_DEFINITION

MatrixNorm avatar Aug 28 '19 17:08 MatrixNorm

I am struggling with the same problem. I have a Parent component that defines the connection and its arguments and I have multiple children components that have various data requirements towards that connection, but no claims on what the arguments are.

I want to do something like:

fragment Dashboard_viewer on Viewer {
  routes(filterBy: $filterBy, first: 100) @connection(key: "Dashboard_routes", filters: ["filterBy"]) {
    ...Map_routes
    ...Timeline_routes
    edges {
      node {
        id
        name
      }
    }
  }
}

fragment Map_routes on RouteConnection {
  edges {
    node {
      id
      directions
    }
  }
}

fragment Timeline_routes on RouteConnection {
  edges {
    node {
      id
      name
      segments
    }
  }
}

This actually works, if anyone is wondering how to achieve connection level fragments.

ivosabev avatar Oct 09 '19 08:10 ivosabev

@ivosabev Your example should work since there is an edges field directly on the connection. Do you get an error?

Connections are a difficult point to use fragment spreads, and we don't expect to remove the restriction on having a direct edges selection. In fact, we're exploring a new approach to representing connections that is more flexible in many ways - but to achieve that we will actually disallow fragment spreads on (new) connection fields. If you're trying to achieve a reusable connection component, you can generally do so by defining an edge fragment and passing the array of edges, pageInfo, and pagination callbacks (from PaginationContainer) to the connection component.

josephsavona avatar Oct 09 '19 14:10 josephsavona

Huh, I've tried the example that was putting @connection() on the child fragments and got @MatrixNorm's error and assume that my example would not work, but it ACTUALLY does.

@josephsavona thanks for your response and clarification, maybe connection and edge level fragments should be documented with examples somewhere as they are somewhat hard to understand.

ivosabev avatar Oct 09 '19 15:10 ivosabev

@ivosabev does that also work with having your Map and Timeline components as pagination containers? Or do you still have to hardcode the parent type (in this case Viewer) in connectionConfig.query?

I'm currently in the same boat as @taiki-t and have a connection that can have multiple parent types. Would be nice to have a reusable pagination container which I can use for both cases. In my case I have a SpeciesList component which paginates on the SpeciesConnection; which appears in Query.allSpecies but also in for example Query.genus(...).species.

CrocoDillon avatar Oct 09 '19 20:10 CrocoDillon

@CrocoDillon

I have not tried. I am using simple fragment containers. My case is that I do not care what items are going to the child components. I just want to make sure they are the same and the data requirements of the children are clearly stated inside their definitions.

ivosabev avatar Oct 10 '19 06:10 ivosabev

This seems to work now.

sorenhoyer avatar Jan 15 '20 23:01 sorenhoyer

Ace, thanks for following-up @sorenhoyer :+1:

alloy avatar Jan 28 '20 13:01 alloy

Sorry @alloy, I need to correct myself.

Specifying the edges field does indeed still seem to be required (at least in the latest experimental version), but if you want to use the @connection directive you can easily get around it by doing

edges {
  __typename
}

Alternative as @josephsavona mentioned you can use @__clientField(handle: "connection") instead of @connection and leave out edges

In my opinion those 2 solutions are not that bad, but since @josephsavona mentions that they're looking into finding a better solution, perhaps this should be re-opened again.

Sorry for the false report!

sorenhoyer avatar May 19 '20 10:05 sorenhoyer

Thanks for following up yet again 👍

alloy avatar May 19 '20 13:05 alloy

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Dec 25 '20 15:12 stale[bot]

Seeing that this is now stale, wanted to check in to see if there are any plans to address this in an upcoming release so the workaround is no longer needed?

theseyi avatar Jun 27 '22 05:06 theseyi

Still interest in this being addressed as I am running into all the above issues 7 years down the line 😅

adstr123 avatar Aug 08 '24 13:08 adstr123