How can I have multiple sibling instances of the same top level fragment container?

1.1k Views Asked by At

I am following this guide: https://relay.dev/docs/en/quick-start-guide#composing-fragments

I am trying to create a higher level fragment container that queries data from the RootQuery:

export const FormInstanceListContainer: FunctionComponent<Props> = props => {
  const { data, ...rest } = props
  const { formInstances } = data

  return (
    <FormInstanceList
      formInstances={formInstances}
      {...rest} // Forward all the other props
    />
  )
}

export default createFragmentContainer(FormInstanceListContainer, {
  data: graphql`
    fragment FormInstanceListContainer_data on RootQuery
      @argumentDefinitions(status: { type: "FormStatus" }) {
      formInstances(status: $status) {
        id
        form {
          id
          name
        }
        status
        createdAt
        submittedAt
      }
    }
  `,
})

This works well as long as I only need one of these lists rendered. Here is a usage example:

const IndexPage: NextPage<QueryResponse> = data => {
  const handleOpenClick = (formInstance: FormInstance) => {
    NextRouter.push(`/form-instance/${formInstance.uuid}`)
  }

  const handleEditClick = (formInstance: FormInstance) => {
    NextRouter.push(`/form-instance/${formInstance.uuid}/edit`)
  }

  return (
    <DefaultLayout>
      <Container maxWidth="md">
        <Typography variant="h5" style={{ marginBottom: 25 }}>
          My drafts
        </Typography>

        <FormInstanceListContainer
          data={data}
          onOpenClick={handleOpenClick}
          onEditClick={handleEditClick}
        />
      </Container>
    </DefaultLayout>
  )
}

export default withData<pages_dashboard_Query>(IndexPage, {
  query: graphql`
    query pages_dashboard_Query {
      ...FormInstanceListContainer_data @arguments(status: DRAFT)
    }
  `,
})

Unfortunately, I need 2 of these lists rendered side by side... One for draft forms and one for submitted forms.

I can't just include expand the same fragment again:

    query pages_dashboard_Query {
      ...FormInstanceListContainer_data @arguments(status: DRAFT)
      ...FormInstanceListContainer_data @arguments(status: SUBMITTED)
    }

ERROR: Expected all fields on the same parent with the name or alias 'formInstances' to have the same name and arguments.

How then can I have more than one FormInstanceListContainer on the same page with different data? Have I hit a dead end the way I've designed my fragment container?


The problem would be solved if I was able to run the queries in the top level page (since then I could use aliases) and pass the list of results to the FormInstanceListContainer. To do that it seems to me that the FormInstanceListContainer must request the fields of the query rather than the query itself:

export default createFragmentContainer(FormInstanceListContainer, {
  formInstance: graphql`
    fragment FormInstanceListContainer_formInstance on FormInstance {
      id
      form {
        id
        name
      }
      status
      createdAt
      submittedAt
    }
  `,
})

However, now Relay assumes that a single instance of FormInstance should be passed to the container, not a list of them. Any way I try to pass a list causes Relay to crash. Is it possible to instruct Relay that it should expect a list of values rather than a single value?

I am completely stuck.

2

There are 2 best solutions below

0
On

I think I've encountered this issue, and I think I solved it as you suggest, by including the fields that needs to aliased up in the query:

graphql`
   fragment FormInstanceFields on FormInstance {
      id
      form {
        id
        name
      }
      status
      createdAt
      submittedAt
   }
`
export default withData<pages_dashboard_Query>(IndexPage, {
  query: graphql`
    query pages_dashboard_Query {
      drafts: formInstances(status: DRAFT) {
        ...FormInstanceFields
      }
      submitted: formInstances(status: SUBMITTED) {
        ...FormInstanceFields
      }
    }
  `,
})

(Above fragment needs to be named correctly per compiler requirements)

Then the thing to do is to not wrap FormInstanceList in a fragment container, because as you point out, this leads to errors. The impulse to wrap it is strong, because using the HOC-based API, the instinct is to wrap all the way down through the render tree. But that won't work in this case.

Instead, wrap each FormInstance in a fragment container (which I'm guessing you're doing already already). Then it will work. The drafts and submitted fields will contain arrays of Relay store refs, and you can interate over them and pass each one to FormInstanceContainer.

This may feel wrong, but if you think of it from the perspective of the upcoming hooks API, or relay-hooks, there's nothing really wrong it.


EDIT

Haven't used Relay in a while, but I think you could even keep the containers at all levels by doing something like this:

graphql`
  fragment FormInstanceListContainer_data on RootQuery {
      drafts: formInstances(status: DRAFT) {
        ...FormInstanceFields
      }
      submitted: formInstances(status: SUBMITTED) {
        ...FormInstanceFields
      }
  }
`

Shouldn't that work, too?

0
On

Relay does not allow to spread multiple fragments inside single query only if these fragments fetching data from same table with different where clause conditions.

All you need to do is to create single fragment instead of multiple like follwoing.

// Your Query...
graphql`
 query MyQuery($currentTs: bigint!) {
   ...MyFragment @arguments(currentTs:  $currentTs)
 }  


// Your Fragment...
fragment MyFragment on query_root @argumentDefinitions(
 currentTs: {type: "bigint"},     
) {
 one: appointments_connection(where: { package_type_id: { _eq: 4 }, real_status: { _in: [1, 8] }, ends_at: { _gt: $currentTs } }, first: 1, order_by: { starts_at: asc }) { 
      id 
      title
     }
 two:  appointments_connection(where: {package_type_id: {_eq: 1}}, first: 1) {
    id_random
    }
 three: appointments_connection(where: {real_status: {_in: [1, 8]}, ends_at: {_gt: $currentTs}}, first: 3, order_by: {starts_at: asc}) {
    title
    starts_at
   }
  }

Hope this will help to those who are new in relay.