contentful / contentful.js

JavaScript library for Contentful's Delivery API (node & browser)
https://contentful.github.io/contentful.js
MIT License
1.19k stars 200 forks source link

v10 missing mechanism to reference resolved types when typecasting #1921

Open jacksonopp opened 1 year ago

jacksonopp commented 1 year ago

Expected Behavior

When typecasting data, I should be able to have correct type definitions when accessing resolved links. This is frequently required if you are rendering a RichText content type.

In this example, let's have a BlogPost containing a Hero content type which has some references to a CallToAction content type.

// These types were generated via the cf-content-types-generator library as recommended in the docs

export interface TypeBlogPost {
  content: EntryFieldTypes.RichText
}

export interface TypeHeroFields {
  title: EntryFieldTypes.Symbol;
  primaryCta?: EntryFieldTypes.EntryLink<TypeCtaSkeleton>;
  /** TypeImageSkeleton is a wrapper around an asset */
  heroImage?: EntryFieldTypes.EntryLink<TypeImageSkeleton>;
}
// ... with the correct skeleton type

export interface TypeCta {
  linkText: EntryFieldTypes.Symbol;
  url: EntryFieldTypes.Symbol;
}
// ... with the correct skeleton type

Using a rich text renderer, we need to switch through the result of a query and build components based on the content types. Since the RichText entry field type has no mechanism for explaining what the underlying data is in the resolved rich text links, we have to resort to typecasting that data.

The following code snippet is pseudocode which is similar to most of the rendering patterns across frameworks.


// 1. Get the data using the cdaClient.getEntries

// 2. Set up a renderer
// -- function to render the resolved component
const renderers = {
  [BLOCKS.EMBEDDED_ENTRY] = (node) => {
    switch (nodeType /* extract the nodeType from the node param */) {
      case 'hero':
        return HeroComponent
    }
  }
}

// 3. Create the component
// -- a psueudocode component representing a 'Hero component'
function HeroComponent = () => ({
  data: TypeHeroFields // renderers will provide the data somewhere

  /*
    The type of `data.primaryCta` contains `entry and type`
    But the actual data in the log contains `metadata, sys, and fields`
  */
  console.log(data.primaryCta)
})

Actual Behavior

I need to cast the types manually, which can be brittle if there are multiple layers linked

function HeroComponent = () => ({
  data: TypeHeroFields

  primaryCtaUrl = (data.primaryCta as unknown as TypeCtaFields).url

 // etc
})

Possible Solution

Create a recursive type which allows you to specify resolved types

Context

I have fairly deeply nested references in my blog post components, and we are frequently updating the content model. Using the recommended auto-generation tools, or following the typescrip tutorial (which is pretty short and doesn't explain in too much detail) plus the type casting on multiple layers inside my rendered components is super brittle and prone to breakage.

Environment

VDinets commented 1 year ago

Any updates?

sdornan commented 1 year ago

Running into this as well trying to upgrade Contentful.js to v10.