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">>'.