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