GraphQL fragments and Redwood

Hi everyone,

First of all, I did not have a chance yet to thank you all for your dedicated time to make such an awesome tool, so here it is!

TLDR;
Where should I declare GraphQL fragments in order to be available and recognized by Typescript?


To factorize gql queries, I’m using GraphQL Fragments with half success, everything works as intended on Apollo’s side (queries are sucessful), but typescript is stuck as it is unaware of the declaration of fragments.

Currently, I’m using it as such, and it works:

web/src/fragments/events.ts

export const EventFragment = gql`
  fragment EventFragment on Event {
    [...]
  }
`
[...]

web/src/components/Event/EventsCell


import { EventFragment } from "src/fragments/events";

const QUERY_TANK = gql`
  query FindEventsTankQuery($take: Int, $skip: Int, $idTank: Int!) {
    events: eventsByTank(take: $take, skip: $skip, idTank: $idTank) {
      ...EventFragment
    }
  }
  ${EventFragment}
`
const QUERY_SITE = gql`
  query FindEventsSiteQuery($take: Int, $skip: Int, $idSite: Int!) {
    events: eventsBySite(take: $take, skip: $skip, idSite: $idSite) {
      ...EventFragment
    }
  }
  ${EventFragment}
`

/**
 * https://github.com/redwoodjs/redwood/blob/main/packages/web/src/components/createCell.tsx#L133
 * The GraphQL syntax tree to execute or function to call that returns it.
 * If `QUERY` is a function, it's called with the result of `beforeQuery`.
 */
export const QUERY = ({variables}) =>{
  if(variables.idTank)
    return QUERY_TANK
  else 
    return QUERY_SITE
}

Byt my fragments are marked as errored by Typescript, both with yarn rw g types and as I hover the red-underlined fragments on my code:

Unknown fragment “EventFormFragment”

I guess redwood doesn’t include the fragments when it generates the types, as it doesn’t know about it.

I tried:

  • writing fragments on my src/graphql/.sdl.ts* file, but then I can’t import it on my Cell, and Types aren’t generated either
  • patching my fragments directly on .redwood/schema.graphql, just to see, no success
  • moving my fragments in api, and including documents on graphql.config.ts, as seen on this github thread with:
    • documents: ["./api/src/fragments/*.ts"]
    • documents: "./api/src/fragments/*.ts"
    • documents: "api/src/fragments/*.ts"

Graphql.config.ts :

const { getPaths } = require('@redwoodjs/internal')

module.exports = {
  schema: getPaths().generated.schema,
  documents: ["./api/src/fragments/*.ts"] //https://the-guild.dev/graphql/codegen/docs/config-reference/documents-field#glob-expression
}

I left tsconfig.json untouched.

I’m wondering where should I ideally inject these fragments in Redwood scheme? Any idea (other than @ts-expect-error everywhere)?

Thanks for your time!

Update

I can find my types on web/types/graphql.d.ts when I edit tsconfig.json to include my fragments, when it’s located on web, but typescript still complains:

tsconfig.json

{
  [...]
  "include": [
    "src",
    "../.redwood/types/includes/all-*",
    "../.redwood/types/includes/web-*",
    "../types",
    "./types", 
    "src/fragments"  
  ]
}

web/types/graphql.d.ts

export type EventFormFragment = { __typename?: 'Query', […]
3 Likes

Hi! Improving the DX is on my task list for v6.

I’ve seen similar behavior.

Am actually meeting with some experts tomorrow to discuss fragments and hope to have so updates this week.

I’ll update here to let you know progress.

4 Likes

Thanks for your feedback. Let me know if I can help!

I’m new to Redwood and am having trouble figuring out what patterns to use for data specification on the front end. In my testing I am having issues when interpolating a gql fragment inside a Cell’s gql string.

I have a file with the fragment:

export const CO_FIELDS = gql`
  fragment FragmentName on GqlType {
    name
  }
`;

which I am importing into my cell file with:

import { CO_FIELDS } from './FragmentFile'

export const QUERY = gql`
  ${CO_FIELDS}
  query QueryName {
    someQuery {
      number
      ...FragmentName
    }
  }
`

When running yarn rw g types I get a GraphQLError: Syntax Error: Unexpected <EOF>.

Are there any updates on this feature, or am I doing something wrong?

Hi @Cantrip and thanks fro using RedwoodJS and also giving fragments a go.

In short, the way RedwoodJS performs codegen means it cannot load documents (queries) where there is interpolation: the ${CO_FIELDS}.

But - much improved and commented fragment support is coming in he next release!

This PR is available in a canary release and will be in the next rc as well: https://github.com/redwoodjs/redwood/pull/9140

You’ll then be able to do:

import type { Fruit } from 'types/graphql'

import { registerFragment } from '@redwoodjs/web/apollo'

import Card from 'src/components/Card/Card'
import Stall from 'src/components/Stall'

const { useRegisteredFragment } = registerFragment(
  gql`
    fragment Fruit_info on Fruit {
      id
      name
      isSeedless
      ripenessIndicators
      stall {
        ...Stall_info
      }
    }
  `
)

const Fruit = ({ id }: { id: string }) => {
  const { data: fruit, complete } = useRegisteredFragment<Fruit>(id)

  console.log(fruit)

  return (
    complete && (
      <Card>
        <h2 className="font-bold">Fruit Name: {fruit.name}</h2>
        <p>Seeds? {fruit.isSeedless ? 'Yes' : 'No'}</p>
        <p>Ripeness: {fruit.ripenessIndicators}</p>
        <Stall id={fruit.stall.id} />
      </Card>
    )
  )
}

export default Fruit

and

import type { Fruit } from 'types/graphql'

import { registerFragment } from '@redwoodjs/web/apollo'

import Card from 'src/components/Card/Card'
import Stall from 'src/components/Stall'

const { useRegisteredFragment } = registerFragment(
  gql`
    fragment Fruit_info on Fruit {
      id
      name
      isSeedless
      ripenessIndicators
      stall {
        ...Stall_info
      }
    }
  `
)

const Fruit = ({ id }: { id: string }) => {
  const { data: fruit, complete } = useRegisteredFragment<Fruit>(id)

  console.log(fruit)

  return (
    complete && (
      <Card>
        <h2 className="font-bold">Fruit Name: {fruit.name}</h2>
        <p>Seeds? {fruit.isSeedless ? 'Yes' : 'No'}</p>
        <p>Ripeness: {fruit.ripenessIndicators}</p>
        <Stall id={fruit.stall.id} />
      </Card>
    )
  )
}

export default Fruit

and also use in cells.

This approach relies on Apollo’s fragment registry so that the client knows about the fragments, and no interpolation is needed.

Hope this helps.

2 Likes

I currently have a working setup that is the following:

file with fragment:

export const CALENDAR_FRAGMENT = gql`
  fragment CalendarFragment on Calendar {
    id
    userId
    timeSlots
  }
`

file using fragment:

import { CALENDAR_FRAGMENT } from './FragmentFile'

export const QUERY = gql`
  query FindCalendarById($id: String!) {
    calendar(id: $id) {
      ...CalendarFragment
    }
  }
  ${CALENDAR_FRAGMENT}
`

I did not have to define any custom types.

I’m not able to see a major difference between what you have and my setup so just throwing a few guesses out there. Could it be that your someQuery is not returning a GqlType ? (My calendar query returns a Calendar.

Also, could the error be from elsewhere? The actual source of a graphQL error is currently a bit difficult to detect. I try to mitigate this by running rw g types frequently.

Hope this rings some bell for you.

1 Like

Redwood fragment documentation

The docs for Fragments are part of v7. You can see them as part of the canary release docs until it gets out of rc/ Fragments | RedwoodJS Docs

Also, there is an example app here: redwood/__fixtures__/fragment-test-project/web/src/pages/GroceriesPage/GroceriesPage.tsx at main · redwoodjs/redwood · GitHub

1 Like

Thanks for the update!

In my case, all the fragments are defined in a separate files, and were used as this (with unavoidable typescript errors) :

src/fragments/events:

export const EventFragment = gql`
  fragment EventFragment on Event {
    id
    description
    [...]

src/components/EditEventCell:

import { EventFragment } from "src/fragments/events"

[...]

export const QUERY = gql`
  query FindEditEventQuery($id: Int!, $date: Date) {
    event: event(id: $id) {
      ...EventFragment
    }
  }
  ${EventFragment}
`

As of v7, I thought that I could just drop in registerFragments on my file, and made them available for Apollo by selectively importing those:

src/fragments/events:

export const EventFragment = registerFragment(gql`
  fragment EventFragment on Event {
    id
    description
    [...]

src/components/EditEventCell:

import { EventFragment } from "src/fragments/events"

[...]

export const QUERY = gql`
  query FindEditEventQuery($id: Int!, $date: Date) {
    event: event(id: $id) {
      ...EventFragment
    }
  }
`

This didn’t work, I had a Unknown fragment EventFragment.
Experimenting with the fragment test repo, I find out that it’s because the registerFragment is not executed, as the imported component is never used.

Solution #1

Export nothing, execute the code directly, import the whole file
Pros: works
Cons: not ideal, because I grouped all my model’s fragment on the same file

src/fragments/events:

import { registerFragment } from "@redwoodjs/web/apollo"

registerFragment(gql`
  fragment EventFragment on Event {
    id
    description
    [...]
}
[...]

src/components/EditEventCell:

import "src/fragments/events"

[...]

export const QUERY = gql`
  query FindEditEventQuery($id: Int!, $date: Date) {
    event: event(id: $id) {
      ...EventFragment
    }
  }
`

Solution #2

Export functions to execute the registerFragment, and call it first.
Pros: import only what I need
Cons: weird workaround

src/fragments/events:

import { registerFragment } from "@redwoodjs/web/apollo"

export const EventFragment = () => registerFragment(gql`
  fragment EventFragment on Event {
    id
    description
    [...]
}
[...]

src/components/EditEventCell:

import { EventFragment } from "src/fragments/events"

EventFragment(); // Duh

[...]

export const QUERY = gql`
  query FindEditEventQuery($id: Int!, $date: Date) {
    event: event(id: $id) {
      ...EventFragment
    }
  }
`

Any thoughts on how to deal with this properly ?

I have the exact same issue. Also using the “import whole file” trick right now to get the fragment actually found. The docs are very unclear on this point… what exactly am I supposed to import from where?

Just wanting to add that I have the same issue as well and I’m also doing the “import the whole file” workaround. The docs make it seem, as @Bigood points out, that you can just drop in registerFragment and your fragments will be recognized but this isn’t the case. We’re thinking possibly a solution would be to import fragments at the top level, such as in the <App> component and call registerFragments there to guarantee availability but this also doesn’t seem like a great experience.

I don’t know if this helps, but this is how Jerel from Apollo set it up with Redwood for a workshop at the October conference:

each 00-setup, 01-component-fragments has a ReadMe to go through and as of part 02 you can see the “completed components”

Frag’d components vs. Non-frag’d components in part 02->web->src->workshop

Cell with fragments example