I'm building a Next.js app in TypesScript and one of the type is:
type GetStaticPropsContext<Q extends ParsedUrlQuery = ParsedUrlQuery, D extends PreviewData = PreviewData> = {
params?: Q | undefined;
preview?: boolean | undefined;
previewData?: D | undefined;
locale?: string | undefined;
locales?: string[] | undefined;
defaultLocale?: string | undefined;
}
I want to change it so that locale, locales, and defaultLocale are never undefined to avoid writing as string and as string[] castings everywhere since in my application, locales are always defined.
I tried creating a type like this
type LocaleConfig = {
locale: string
defaultLocale: string
locales: string[]
}
And then just using
export const getStaticProps: GetStaticProps = (
context: GetStaticPropsContext & LocaleConfig
) { // do stuff }
But I am getting a Type 'string | undefined' is not assignable to type 'string'. error.
I also tried to create an interface extending GetStaticPropsContext but unless I misunderstood how this works, I will also have to copy over the generics like this (which is not ideal):
export interface NewGetStaticPropsContext<
Q extends ParsedUrlQuery = ParsedUrlQuery,
D extends PreviewData = PreviewData
> extends GetStaticPropsContext {
params?: Q | undefined
previewData?: D | undefined
locale: string
defaultLocale: string
locales: string[]
}
CodePudding user response:
What very probably happens is that GetStaticProps type is actually what causes the error message:
Should it look like:
type GetStaticProps = (context: GetStaticPropsContext) => void;
...then it is the assignment into getStaticProps variable of a function that has some required argument properties that TS does not like: you can assign a more "permissive" function (e.g. one that accepts optional arguments, into a stricter one, i.e. that accepts required arguments), but not the reverse.
type GetStaticPropsContext<Q extends ParsedUrlQuery = ParsedUrlQuery, D extends PreviewData = PreviewData> = {
params?: Q | undefined;
preview?: boolean | undefined;
previewData?: D | undefined;
locale?: string | undefined;
locales?: string[] | undefined;
defaultLocale?: string | undefined;
}
type LocaleConfig = {
locale: string
defaultLocale: string
locales: string[]
}
export const getStaticProps = (
context: GetStaticPropsContext & LocaleConfig
) => { } // Okay
type GetStaticProps = (context: GetStaticPropsContext) => void
const getStaticProps2: GetStaticProps = getStaticProps;
// Type '(context: GetStaticPropsContext & LocaleConfig) => void' is not assignable to type 'GetStaticProps'.
// Type 'string | undefined' is not assignable to type 'string'.
// Type 'undefined' is not assignable to type 'string'.
By saying that getStaticProps2 function is of GetStaticProps type, it means that we could call it with an empty argument (all properties in GetStaticPropsContext are optional):
getStaticProps2({});
But if getStaticProps is assigned into it, some properties would become required...
As to transform a type so that some of its optional properties become required, indeed, using an intersection (&) is the appropriate technique.
You can even use a combination of Required and Pick utility types to catch typos and ensure the exact same types:
type LocaleConfig = Required<Pick<GetStaticPropsContext, "locale" | "defaultLocale" | "locales">>
// ^? { locale: string; defaultLocale: string; locales: string []; }
export const getStaticProps = (
context: GetStaticPropsContext & LocaleConfig
) => {
// do stuff
const locale = context.locale
// ^? string
const preview = context.preview
// ^? boolean | undefined
}
getStaticProps({
locale: "locale",
defaultLocale: "",
//locales: [] // Error if any of the Required properties is missing
});
// Property 'locales' is missing in type '{ locale: string; defaultLocale: string; }' but required in type 'Required<Pick<GetStaticPropsContext<ParsedUrlQuery, PreviewData>, "locale" | "defaultLocale" | "locales">>'.
