#Why narrowing doesnt work?

29 messages · Page 1 of 1 (latest)

grim heron
#

Types of property 'icon' are incompatible.
Type 'Element | undefined' is not assignable to type 'Element'.
Type 'undefined' is not assignable to type 'ReactElement<any, any>'.

type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<T, Exclude<keyof T, Keys>> &
  {
    [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>
  }[Keys]

export interface BaseButton {
  onClick: () => void
  acl?: ToArg<Abilities>
}

type IconType = JSX.Element

export interface RegularButton extends BaseButton {
  text: string
  icon?: IconType
}

export interface IconButton extends BaseButton {
  icon: IconType
}

export type HeaderButton = RequireAtLeastOne<RegularButton & IconButton, 'text' | 'icon'>

  const [iconButtons, regularButtons] = !buttons
    ? [[], []]
    : buttons
        .filter((button) => !button.acl || aclAbility.can(button.acl))
        .reduce(
          (acc, button) => {
            if (button.icon && !button.text) {
              acc[0].push(button)
            } else {
              acc[1].push(button)
            }

            return acc
          },
          [[], []] as [Array<IconButtonType>, Array<RegularButton>],
        )
boreal juniper
#

@grim heron can you share a more complete example (including definitions for buttons, ToArg, etc)?

grim heron
boreal juniper
#

i'm trying to reproduce your error so that i can help you debug it. i'm not sure where i should be looking. so far i have this:

little pivotBOT
#
mkantor#0

Preview:```ts
import "react"

type RequireAtLeastOne<
T,
Keys extends keyof T = keyof T

= Pick<T, Exclude<keyof T, Keys>> &
{
[K in Keys]-?: Required<Pick<T, K>> &
Partial<Pick<T, Exclude<Keys, K>>>
}[Keys]

export interface BaseButton {
onClick: () => void
acl?: ToArg<Abilities>
...```

boreal juniper
#

what line is that error on?

grim heron
#

36 and 38

boreal juniper
#

ah i see IconButtonType was supposed to be IconButton. i thought it was another missing definition

grim heron
#

import 'react'

type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<T, Exclude<keyof T, Keys>> &
  {
    [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>
  }[Keys]

export interface BaseButton {
  onClick: () => void
}

type IconType = JSX.Element

export interface RegularButton extends BaseButton {
  text: string
  icon?: IconType
}

export interface IconButton extends BaseButton {
  icon: IconType
}

export type HeaderButton = RequireAtLeastOne<RegularButton & IconButton, 'text' | 'icon'>


declare const buttons: Array<HeaderButton>

const [iconButtons, regularButtons] = !buttons
  ? [[], []]
  : buttons
    .reduce(
      (acc, button) => {
        if (button.icon && !button.text) {
          acc[0].push(button)
        } else {
          acc[1].push(button)
        }

        return acc
      },
      [[], []] as [Array<IconButton>, Array<RegularButton>],
    )

#

Should be like this

boreal juniper
#

checking button.icon doesn't narrow button unless button is a discriminated union

#

when you do acc[0].push(button) the button variable there hasn't been narrowed at all with this setup

grim heron
#

I understand, but why it doesnt narrow?

boreal juniper
#

that's just how typescript works. it never narrows a when you check a.b except if a is a discriminated union and b is one of the discriminating properties

#

you could either make your button types form a discriminated union or you could construct a new value instead of using button inside your conditions

#

do you have a preference for one vs the other?

grim heron
#

My purpose to pass into a component array of RegularButton or IconButton types and after that split it into two arrays

grim heron
grim heron
boreal juniper
#

yeah, working on it

#

if you want to use a discriminated union it might look like this:

little pivotBOT
#
mkantor#0

Preview:```ts
import "react"

export interface BaseButton {
onClick: () => void
acl?: ToArg<Abilities>
}

type IconType = JSX.Element

export interface RegularButton extends BaseButton {
type: "regular"
text: string
icon?: IconType
}

export interface IconButton extends BaseButton {
...```

boreal juniper
#

as an added bonus you don't need your RequireAtLeastOne hack anymore

#

since you can guarantee that directly

grim heron
#

export type HeaderButton = RegularButton | IconButton

this will not works because I need to have at least one provided property or icon or text. By this reason I use RequireAtLeastOne type

boreal juniper
#

the types already require that, try it for yourself

#

it's not possible to create a HeaderButton without one of those properties

grim heron
#

Yeah, you're right, it works! Thank you so much