Structured Text with Typescript

Hello everyone! Completely new to DatoCMS after working with Strapi for a while, and Iā€™m having major issues getting the typescript codegen things to work.

Iā€™ve followed this guide but Iā€™m not having any success.

For one, the graphql.config.yml file has a spelling error in the custom scalars:
ā€œJsonField: unkownā€ should be unknown, right?

But when I fix that I get a type error on the StructuredText component:

"Type '{ __typename?: "StartpageModelContentField" | undefined; value: unknown; } | null | undefined' is not assignable to type 'Document | Node | StructuredText<Record, Record> | null | undefined'."
... 
"Type 'unknown' is not assignable to type 'Document'"

In other similar issues on here, a suggested solution is to make the JsonField scalar have the type " StructuredText<Record, Record>", but StructuredText doesnā€™t get imported in to the generated.ts file, and if I import it manually Iā€™m getting the error ā€œGeneric type ā€˜Recordā€™ requires 2 type argument(s).ts(2314)ā€

Any help would be much appreciated, itā€™s definitely possible Iā€™m doing something else completely wrong.

Hello @koskarbengtsson and welcome to the community!

Can you attempt to change it to

JsonField: "StructuredText<Record, Record>

In your graphql.config.yml and let me know if it works after re-generating?

Thank you for the swift response! I get errors in the generated.ts file itself now, but it did fix the error in the components. What I also had to do was restructure the .yml file to this:

...

plugins:
      - typescript
      - typescript-operations
      - typed-document-node
    config:
      strictScalars: true
      scalars:
        BooleanType: boolean
        CustomData: Record<string, string>
        Date: string
        DateTime: string
        FloatType: number
        IntType: number
        ItemId: string
        JsonField: "StructuredText<Record, Record>"
        MetaTagAttributes: Record<string, string>
        UploadId: string
      namingConvention:
        enumValues: "./src/lib/pascalCaseWithUnderscores.js"

Putting the custom scalars indented inside the typescript-operations plugin made all the scalars in the generated file ā€œanyā€ for some reason.

Any ideas on how to fix the type errors in generated.ts? It wonā€™t compile and canā€™t be excluded in my tsconfig.

Iā€™ve sort of kinda made it work by changing my .yml file to this:

generates:
  src/graphql/generated.ts:
    plugins:
      - typescript
      - add:
          content: "import { type Record as StructuredTextGraphQlResponseRecord, type StructuredText as StructuredTextGraphQlResponse } from 'datocms-structured-text-utils';"
      - typescript-operations
      - typed-document-node
    config:
      strictScalars: true
      scalars:
        BooleanType: boolean
        CustomData: Record<string, string>
        Date: string
        DateTime: string
        FloatType: number
        IntType: number
        ItemId: string
        JsonField: "StructuredTextGraphQlResponse<StructuredTextGraphQlResponseRecord,StructuredTextGraphQlResponseRecord>"
        MetaTagAttributes: Record<string, string>
        UploadId: string
      namingConvention:
        enumValues: "./src/lib/pascalCaseWithUnderscores.js"

and then adding the full ā€œpage.content.valueā€ prop to the StructuredText data field.
But I imagine that would break cases where you need to use blocks or links instead of the value.

Iā€™m facing the same issue with this one. Any chance of an update with a proper answer for this @m.finamor? @koskarbengtsson said so himself, that using blocks or links would break and I need to use links in my case. Iā€™m getting errors in my generated.ts file:
Generic type 'Record' requires 2 type argument(s).

Hi @john.hult, welcome! Could you please provide a bit more detail?

I tried following the guide and using the example repo along with a structured text field with a linked record, and it seems to work without any errors.

I didnā€™t modify the graphql.config.yml, but I did edit graphql/home.graphql in that example to fetch the structured context field and its links, like:

query Home {
  allArticles {
    id
    title
    _createdAt
    _publishedAt
    image {
      ...responsiveImage
    }
    content {
      value
      links {
        id
        title
        content {
          value
        }
      }
    }
  }
}

(The content is what I added over the example)

Then I as able to run graphql-codegen without any errors.

What specifically are you trying to do that is failing with an error? Any error messages, screenshots, sample code, etc. would be very helpful :slight_smile:

I ended up doing a custom validation for the value,links,block field. The problem was that we had something like this:

someStructuredText {
      value
      links {
        __typename
        ... on PageRecord {
          pageTitle
          url
          id
        }
      }
    }

and then a component that wraps the StructuredContent as such

<Content
    data={parseStructuredText(someStructuredText)}
    linkProps={{ bold: true, underline: true }}
/>

and lastly, we have the Content component like this

interface R1 extends StructuredTextGraphQlResponseRecord {}
interface R2 extends StructuredTextGraphQlResponseRecord {}

interface ContentProps {
  data:
    | StructuredTextGraphQlResponse<R1, R2>
    | StructuredTextDocument
    | Node
    | null
    | undefined;
...

Here, TS would complain when sending the data, as it would not match, since value in this case would be unknown.

I ended up creating the parseStructuredText as you see in the code above, to verify that value exists else Iā€™d just get:

So I did the following:

function isStructuredText(
  text: unknown
): text is StructuredTextGraphQlResponse {
  const t = text as StructuredTextGraphQlResponse;
  return (
    t &&
    typeof t === "object" &&
    "value" in t &&
    validate(t.value as Document).valid
  );
}

/**
 * if t contains value and is validated as a Document ()
 * @param t your structured text
 * @returns Structured Text
 */
export function parseStructuredText(
  t: unknown
): StructuredTextGraphQlResponse | undefined {
  return isStructuredText(t)
    ? (t as unknown as StructuredTextGraphQlResponse)
    : undefined;
}

which solves it for now. But it just feels like structured text should have a proper type.

@john.hult, thank you so much for sharing the details! Please give us a few days to look more deeply into it, and weā€™ll report back here ASAP.

Glad the workaround works for now though.

1 Like