Git Product home page Git Product logo

Comments (13)

lancegliser avatar lancegliser commented on May 30, 2024 2

With thanks to @marcolink, I got this working. Here's what I ended up making these:
I can safely reach fields.title as string using this setup.

Client: src/lib/contentful.copy.ts

import client from "./client";
import { TypeCopySkeleton } from "./generated";
import type { Entry } from "contentful";

enum Slugs {
  GenericContentThankYou = "generic-contact-thank-you",
}

// https://app.contentful.com/spaces/****/content_types/copy/fields
const content_type = "copy";

export type CopyTypeWithoutUnresolvableLinksAndASingleLocale = Entry<
  TypeCopySkeleton,
  "WITHOUT_UNRESOLVABLE_LINKS"
>;

type GetParams = {};

export const getCopyGenericContentThankYouWithoutLocales = async (
  params?: GetParams,
): Promise<CopyTypeWithoutUnresolvableLinksAndASingleLocale> =>
  getCopy({
    ...params,
    slug: Slugs.GenericContentThankYou,
  });

type GetParamsWithSlug = GetParams & {
  slug: string;
};
const getCopy = async (
  params: GetParamsWithSlug,
): Promise<CopyTypeWithoutUnresolvableLinksAndASingleLocale> => {
  const query = {
    limit: 1,
    include: 10,
    // locale: params.locale,
    "fields.slug": params.slug,
    content_type,
  };
  const {
    items: [copy],
  } = await client.withoutUnresolvableLinks.getEntries(query);
  if (!copy) {
    throw new Error(`Contentful copy ${params.slug} undefined`);
  }
  return copy as CopyTypeWithoutUnresolvableLinksAndASingleLocale;
};

Component: src/components/Copy/index.tsx

import { ReactNode } from "react";
import RichText from "../RichText";
import { CopyTypeWithoutUnresolvableLinksAndASingleLocale } from "../../../lib/contentful/contentful.copy";

export type CopyProps = {
  fields: CopyTypeWithoutUnresolvableLinksAndASingleLocale["fields"];
};
export default function Copy({ fields }: CopyProps): ReactNode {
  return (
    <div>
      <RichText document={fields.body} />
    </div>
  );
}

Story: src/components/Copy/index.stories.tsx

import { Meta, StoryObj } from "@storybook/react";
import Component from "./index";
import { CopyTypeWithoutUnresolvableLinksAndASingleLocale } from "../../../lib/contentful/contentful.copy";
import { BLOCKS } from "@contentful/rich-text-types";

const copy: CopyTypeWithoutUnresolvableLinksAndASingleLocale["fields"] = {
  title: "Generic contact thank you",
  slug: "generic-contact-thank-you",
  apiIntegrated: true,
  body: {
    data: {},
    content: [
      {
        data: {},
        content: [
          {
            data: {},
            marks: [],
            value: "Thank you for your message.",
            nodeType: "text",
          },
        ],
        nodeType: BLOCKS.PARAGRAPH,
      },
      {
        data: {},
        content: [
          {
            data: {},
            marks: [],
            value: "Someone will get back to you shortly.",
            nodeType: "text",
          },
        ],
        nodeType: BLOCKS.PARAGRAPH,
      },
    ],
    nodeType: BLOCKS.DOCUMENT,
  },
};

// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
const meta: Meta<typeof Component> = {
  component: Component,
  // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/7.0/react/writing-docs/docs-page
  tags: ["autodocs"],
  parameters: {
    // More on Story layout: https://storybook.js.org/docs/react/configure/story-layout
    layout: "centered",
  },
  // More on argTypes: https://storybook.js.org/docs/react/api/argtypes
  argTypes: {},
  args: {
    fields: copy,
  },
};

export default meta;
type Story = StoryObj<typeof Component>;

// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
export const Default: Story = {
  // More on args: https://storybook.js.org/docs/react/writing-stories/args
  args: {},
};

from cf-content-types-generator.

marcolink avatar marcolink commented on May 30, 2024 1

Hi @G-Rath,
Thanks for this very detailed comment. I think you outlined some interesting points. If I understand you correct, the challenge is the typing of the resolving types from a client call.

I made this little sandbox example, to showcase an approach where the resolving type is explicitly defined to pass it around your code. Please let me know if this is make things more clear. Eventually we can extend the generator with more predefined types.

https://codesandbox.io/s/contentful-cda-rest-client-typescript-5ftx3d?file=/src/index.ts

from cf-content-types-generator.

G-Rath avatar G-Rath commented on May 30, 2024 1

@marcolink that helps! I'll look deeper into it over the week as I continue working on my contentful project and let you know how it goes.

from cf-content-types-generator.

marcolink avatar marcolink commented on May 30, 2024

Hi @sima995 👋

Any chance to provide an example of how you're currently using it?

from cf-content-types-generator.

sima995 avatar sima995 commented on May 30, 2024

At the time of writing this I wasn't able to figure out a way to use it. After googling around a bit more, I was able to cast the above mentioned example using as unknown as string which is inconvenient but at least the error was gone.

<h1>{fields?.heroTitle as unknown as string}</h1>

from cf-content-types-generator.

marcolink avatar marcolink commented on May 30, 2024

I suggest you try

{fields?.heroTitle ? <h1>{fields?.heroTitle}</h1> : null}

Depending on your setup, a fields value is not always guaranteed.
The type system respects that, and defines the fields value as optional.

from cf-content-types-generator.

sima995 avatar sima995 commented on May 30, 2024

That works as well but I'd still have to cast it as string. Is that how I'm supposed to get the string value or am I missing something?

from cf-content-types-generator.

marcolink avatar marcolink commented on May 30, 2024

btw, are you using contentful@10 - at any older version?

from cf-content-types-generator.

sima995 avatar sima995 commented on May 30, 2024

I'm using v10

from cf-content-types-generator.

marcolink avatar marcolink commented on May 30, 2024

@sima995 would you be able to provide the code for how you exactly generate the types, and how you use them?

from cf-content-types-generator.

maximilianschmitt avatar maximilianschmitt commented on May 30, 2024

I'm also wondering how I can tell cf-content-types-generator to generate the types so that strings are simple string types and not localized objects.

This is the command I'm using:

cf-content-types-generator --typeguard --spaceId $CONTENTFUL_SPACE_ID --token $CONTENTFUL_MANAGEMENT_TOKEN -o __generated__/contentful-types -X

Right now, I have to cast as string everywhere which seems dangerous. If my project ever does introduce localization, then the types all over the codebase are wrong.

It would be great if there was a --no-i18n option or something like that so I don't have to do as string everywhere.

from cf-content-types-generator.

G-Rath avatar G-Rath commented on May 30, 2024

(disclaimer: I've only really started using contentful, so what I'm about to say is going to have incorrect terminology and some over simplifications)

I think this might be a documentation problem more than anything maybe combined with some poor defaults - Contentful seems have different behaviours based on modifiers like the locale, which result in lets say "harder-to-use types" and I can't find any documentation that really calls out how to use them.

So let's try to do a complete example:

My app has a CaseStudy content type:

import type {
  ChainModifiers,
  Entry,
  EntryFieldTypes,
  EntrySkeletonType,
  LocaleCode
} from 'contentful';

export interface TypeCaseStudyFields {
  title: EntryFieldTypes.Symbol;
  headerImage?: EntryFieldTypes.AssetLink;
  description?: EntryFieldTypes.Symbol;
  intro?: EntryFieldTypes.Text;
  content?: EntryFieldTypes.Text;
}

export type TypeCaseStudySkeleton = EntrySkeletonType<
  TypeCaseStudyFields,
  'caseStudy'
>;
export type TypeCaseStudy<
  Modifiers extends ChainModifiers,
  Locales extends LocaleCode
> = Entry<TypeCaseStudySkeleton, Modifiers, Locales>;

Attempting to do stuff directly with the Contentful API client works as I'd expect:

import * as contentful from 'contentful';
import { TypeCaseStudySkeleton } from './types/contentful';

const client = contentful.createClient({
  space: process.env.CONTENTFUL_SPACE_ID,
  accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
  host: 'preview.contentful.com'
});

(async () => {
  const studies = await client.getEntries<TypeCaseStudySkeleton>({
    content_type: 'caseStudy'
  });

  console.log(studies.items[0].fields.title.toUpperCase());
})();

However when you start trying to pass these around things get tricker because you have to pass generic arguments:

import * as contentful from 'contentful';
import { TypeCaseStudy, TypeCaseStudySkeleton } from './types/contentful';

const client = contentful.createClient({
  space: process.env.CONTENTFUL_SPACE_ID,
  accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
  host: 'preview.contentful.com'
});

// @ts-expect-error TS2314: Generic type  TypeCaseStudy  requires  2  type argument(s).
const printStudy = (study: TypeCaseStudy) => {
  console.log(study.fields.title.toUpperCase());
};

(async () => {
  const studies = await client.getEntries<TypeCaseStudySkeleton>({
    content_type: 'caseStudy'
  });

  printStudy(studies.items[0]);
})();

My first attempt to resolve this was to put in the extended values (TypeCaseStudy<ChainModifiers, LocaleCode>) which results errors similar to what others have described here already; it turns out the "default" is TypeCaseStudy<undefined, string>.

I personally am not really blaming anyone in particular for this - I get why the Contentful types are so complex and it's not surprising that the widest possible value for the generics results in the widest error, but I think it would be good to have a section of documentation showcasing some common configurations for the types that people just starting can copy-and-paste in.

I also think it could make sense for the generated types to have default values for these generics which would also help, but I can understand if the way these types are used means what I've described isn't actually the most common default that it feels like to me.

Here's a code example for when you're typing the collection:

(async () => {
  let studies: EntryCollection<TypeCaseStudySkeleton, undefined>;

  try {
    studies = await client.getEntries<TypeCaseStudySkeleton>({
      content_type: 'caseStudy'
    });
  } catch (e) {
    console.error('Errored', e);

    return;
  }

  printStudy(studies.items[0]);
})();

One thing I've not yet figured out is how to type queries - there are some cases around fields. properties that TypeScript doesn't like due to apparently not being indexable; I think its to do with how the types are being constructed that turns it into some kind of indexed type that doesn't include the field? I'll post back here if I make progress on this

from cf-content-types-generator.

sima995 avatar sima995 commented on May 30, 2024

@sima995 would you be able to provide the code for how you exactly generate the types, and how you use them?

This is how I generate the types:
"codegen": "cf-content-types-generator -o src/schema -X -g -s SPACEID -t ACCES_TOKEN"

This is my useContentful hook:

import { EntryCollection, EntrySkeletonType, createClient } from "contentful";
import { useEffect, useState } from "react";

const client = createClient({
  accessToken: import.meta.env.VITE_CONTENTFUL_API_ACCESS_TOKEN,
  space: import.meta.env.VITE_CONTENTFUL_SPACE_ID,
});

const useContentful = <T extends EntrySkeletonType>() => {

  const [results, setResults] = useState<null | EntryCollection<T>>(null);

  useEffect(() => {
    client
      .getEntries<T>()
      .then((results) => {
        setResults(results);
      })
      .catch((error) => console.log(error));
  }, []);

  const fields = results?.items[0].fields;

  return {fields}
}

export default useContentful;

With this then I do this in a React component:

const Hero = () => {
  const { fields } = useContentful<TypeHeroSkeleton>();

  return (
    ...
          <h1>{fields?.heroTitle as string}</h1>
    ...
  );
};

Am I correct that the main difference between how I fetch data and your code that you just provided is the withoutUnresolvableLinks part?

from cf-content-types-generator.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.