Comments (13)
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.
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.
@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.
Hi @sima995 👋
Any chance to provide an example of how you're currently using it?
from cf-content-types-generator.
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.
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.
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.
btw, are you using contentful@10 - at any older version?
from cf-content-types-generator.
I'm using v10
from cf-content-types-generator.
@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.
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.
(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 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)
- v10 typing includes EntryFieldTypes, but mismatches actual API response HOT 4
- "Type does not satisfy the constraint 'EntrySkeletonType'." HOT 7
- Type error after Contentful `10.0.0-beta-v10.32` HOT 4
- oclif dependencies are deprecated HOT 2
- Use with Contentful GraphQL endpoint? HOT 1
- 2.12.3 introduces bug HOT 2
- Property does not exist on type {} | {} HOT 2
- Include description and help text in JSDoc comments? HOT 5
- Support a config file
- Support custom prefixes of generics and interfaces HOT 2
- Command hangs after generating the export
- Generated TypeScript Models Lack File Extensions in Import Statements When Using Node 16 Module Resolution HOT 1
- Fields not typed correctly HOT 2
- Generated field interface does not satisfy the constraint 'EntrySkeletonType' HOT 5
- Generated types don't seem to match those expected by Contentful v10 HOT 2
- Support EU Data Center HOT 2
- Release a new version with updated contentful and allow minor version differences if possible HOT 1
- Type Not Satisfying Entry Requirements HOT 6
- Why can `EntryFieldTypes.Array<EntryFieldTypes.EntryLink<EntrySkeletonType>>` array include `undefined`? HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from cf-content-types-generator.