How to use createResovers to convert a Markdown FIELD (not a file) to HTML in Gatsby

305 Views Asked by At

The standard process for converting MD to HTML in Gatsby is for complete files on the local system.

I need to convert a specific field, NOT a file. The top related question is this one, but as you can see that is using Contentful, which now provides a plugin to solve it.

The API I am using (Airtable) is returning a field that contains Markdown.

The recommended solution is to convert using a resolver...but I cannot fully understand the Gatsby docs on the topic.

The error I see when building is this:

UNHANDLED REJECTION Airtable.FAQ provided incorrect OutputType:
 'Object({ resolve: [function resolve], extensions: Object({ createdFrom: "createResolvers" }) })'

I think I am close, but I don't know if I am meant to create a new type or actually what the resolver is meant to return...a new field?

The field in question is FAQ, which you can see in this example of a query in the GraphQL explorer:

query MyQuery {
  allAirtableManufacturer(filter: {data: {Premium: {eq: true}}}) {
    edges {
      node {
        data {
          Premium
          Manufacturer
          Premium_Manufacturers {
            recordId
            data {
              FAQ
              Downloads_File_Name
              Is_Supplier
            }
            internal {
              type
            }
          }
        }
        recordId
        queryName
      }
    }
  }
}

My understanding is that the resolver can/should add a new field with the markdown content converted to html.

So here is my resolver code, where you can see I am trying to add a field called "FAQ_html" to the Airtable node:

  createResolvers({
    Airtable: {
      FAQ_html: {
        resolve(source: any, args: any, context: any, info: any) {
          return remark().use(html).processSync(source.data.FAQ).contents
        },
      },
    }
  })

My gatsby-config for airtable is:

    resolve: `gatsby-source-airtable`,
      options: {
        apiKey: process.env.AIRTABLE_API_KEY,
        concurrency: 5,
        tables: [
          {
            baseId: `appP5vBdAitw6yyDH`,
            tableName: `Manufacturers`,
            queryName: `Manufacturer`,
            tableView: `AppView_Details_DONOTCHANGE`,
            tableLinks: [`Premium_Manufacturers`],
            separateNodeType: true,
            defaultValues: {
              Company_Description: '',
            },
          },
          {
            baseId: `appP5vBdAitw6yyDH`,
            tableName: 'Premium_Manufacturers',
            tableLinks: [`Manufacturers`],
          },

        ],

The table 'Premium_Manufacturers' is obviously linked as a child to 'Manufacturers'.

However, when I explore in GraphQL I also see them appear as a top-level node called 'Airtable', which I did not expect. You can see that here:

allAirtable {
    edges {
      node {
        data {
          FAQ
          Downloads_File_Name
          Last_update
          Is_Supplier
          Section_Name
          Section_No
          Name
          Cell_Number
          Email
          Rep_Name
          Technical_Rep_Name
          Consolidated_Rep
        }
      }
    }
  }

That's why my resolver uses 'Airtable' as the name of the node, but clearly it does not work.

I also tried changing the config to provide a separate node:

          {
            baseId: `appP5vBdAitw6yyDH`,
            tableName: 'Premium_Manufacturers',
            queryName: 'Premium',
            separateNodeType: true,
            tableLinks: [`Manufacturers`],
          },

so now 'allAirtable' becomes 'allAirtablePremium'.

I tried changing the resolver to use that:

  createResolvers({
    allAirtablePremium: {
      FAQ_html: {
        resolve(source: any, args: any, context: any, info: any) {
          return remark().use(html).processSync(source.data.FAQ).contents
        },
      },
    }
  })

But that throws a warning:

warn `createResolvers` passed resolvers for type `allAirtablePremium` that doesn't exist in the schema.

So clearly it doesn't like the 'all', so then I changed it to remove the 'all' like so:

  createResolvers({
    AirtablePremium: {
      FAQ_html: {
        resolve(source: any, args: any, context: any, info: any) {
          return remark().use(html).processSync(source.data.FAQ).contents
        },
      },
    }
  })

And I am back to the original error, which at least tells me there is something wrong with what I am trying to return (as the error clearly says 'wrong OutputType').

So what should that be returning?

Thanks in advance for any help!

UPDATE 1

The code now compiles, but I don't see my custom field.

This is the schema customisation (just create FAQ_HTML as an empty string to start off):

import { GatsbyNode, CreateSchemaCustomizationArgs } from 'gatsby'

export const createSchemaCustomization: GatsbyNode['createSchemaCustomization'] = async ({
  actions,
}: CreateSchemaCustomizationArgs) => {

  const { createFieldExtension, createTypes } = actions
  createFieldExtension({
    name: "FAQ_HTML",
    extend() {
      return ''
    },
  })

  const typeDefs = `
    type airtablePremium implements Node {
      FAQ_HTML: String @FAQ_HTML
    }
`

  createTypes(typeDefs)
}

The resolver is now like this:

  createResolvers({
    airtablePremium: {
      FAQ_HTML: {
        resolve(source: any, args: any, context: any, info: any) {
          console.log("SOURCE IS", source)
          const faqHtml = remark().use(html).processSync(source.FAQ).contents
          console.log("faqHtml IS", faqHtml)
          return faqHtml;
        },
      },
    },
  })

It compiles but the field does not show up in the GraphQL explorer. No console output either.

1

There are 1 best solutions below

0
On BEST ANSWER

After a nice chat with @rmcsharry, we confirmed that you'd need to create the type first.

createTypes(`
  type AirtablePremium implements Node @infer {
    FAQ_HTML: String
  }
`)

The biggest issue was the type name. In the code, it should be AirtablePremium vs airtablePremium. You can find out the type name in GraphiQL by clicking on the field name in the left column, or holding cmd and clicking on the field in your query.

You'd use the typename in createResolvers as well:

  createResolvers({
    AirtablePremium: {
      FAQ_HTML: {
        resolve(source: any, args: any, context: any, info: any) {
          ...
        },
      },
    },
  })

Update from @rmcsharry

After @Derek Nguyen's very helpful assistance, I found an alternative and easier solution.

If you want to avoid the hassle of creating types, you can go with a much simpler solution using the plugin markdown-to-jsx.

Then you simply wrap the incoming markdown field:

<Markdown>{field}</Markdown>

and it creates a react JSX component on the fly. :)