Home > Software engineering >  Typescript type assertion for "dynamic" record
Typescript type assertion for "dynamic" record

Time:03-22

I seem to be missing something, but the | AlertDynamic seems to break my typing suggestion

type Alert = {
  type: string
  variant: 'danger' | 'warning' | 'success' | 'info'
  message: string
}

type AlertDynamic = (arg: string) => Alert

export const alertTypeAuthentication: Record<string, Alert | AlertDynamic> = {
  incorrectPassword: {
    type: 'Incorrect password',
    variant: 'danger',
    message: 'Adgangskode er forkert.',
  },
  passwordCreated: (email: string) => ({
    type: 'Password created',
    variant: 'success',
    message: `We sendt a message to ${email}`,
  }),
} as const

When just using Record<string, Alert > I don't see a type error for alertTypeAuthentication.incorrectPassword.message

CodePudding user response:

Typescript won't know that incorrectPassword is of type Alert, it will know it's either Alert or AlertDynamic. It doesn't matter that you used as const since the Record type is on the variable alertTypeAuthentication.

You can solve this by letting typescript infer the type itself, like this:

export const alertTypeAuthentication = {
  incorrectPassword: {
    type: 'Incorrect password',
    variant: 'danger',
    message: 'Adgangskode er forkert.',
  },
  passwordCreated: (email: string) => ({
    type: 'Password created',
    variant: 'success',
    message: `We sendt a message to ${email}`,
  }),
}

Or create a wrapper type like this:

type Alert = {
  type: string
  variant: 'danger' | 'warning' | 'success' | 'info'
  message: string
}

type AlertDynamic = (arg: string) => Alert

type AlertTypes = {
  incorrectPassword: Alert
  passwordCreated: AlertDynamic
}

export const alertTypeAuthentication: AlertTypes = {
  incorrectPassword: {
    type: 'Incorrect password',
    variant: 'danger',
    message: 'Adgangskode er forkert.',
  },
  passwordCreated: (email: string) => ({
    type: 'Password created',
    variant: 'success',
    message: `We sendt a message to ${email}`,
  }),
}

CodePudding user response:

alertTypeAuthentication is a Record<string, Alert | AlertDynamic>, which means that the compiler doesn't know that alertTypeAuthentication.incorrectPasswordis an Alert or alertTypeAuthentication.passwordCreated is an AlertDynamic, only that they can be either an Alert or AlertDynamic.

Type predicates allow you to write custom type guarding logic. Since Alert is an object type and AlertDynamic is a function type, we can narrow between the types using those properties on some argument of Alert | AlertDynamic:

type Alert = {
  type: string
  variant: 'danger' | 'warning' | 'success' | 'info'
  message: string
}

type AlertDynamic = (arg: string) => Alert

function isAlert(a: Alert | AlertDynamic): a is Alert {
  return typeof a === 'object';
}

function isAlertDynamic(a: Alert | AlertDynamic): a is AlertDynamic {
  return typeof a === 'function';
}

const alertTypeAuthentication: Record<string, Alert | AlertDynamic> = {
  incorrectPassword: {
    type: 'Incorrect password',
    variant: 'danger',
    message: 'Adgangskode er forkert.',
  },
  passwordCreated: (email: string) => ({
    type: 'Password created',
    variant: 'success',
    message: `We sendt a message to ${email}`,
  }),
};

if (isAlert(alertTypeAuthentication.incorrectPassword)) {
  console.log(alertTypeAuthentication.incorrectPassword.type);
}

if (isAlertDynamic(alertTypeAuthentication.passwordCreated)) {
  console.log(alertTypeAuthentication.passwordCreated("foo"));
}

Check it out on the playground.

  • Related