1

I have these types:

export enum LayersItemOptionsEnum {
  OPERATOR,
  HEADER,
}

type sharedTypes = {
  children: string | ReactElement;
};

type LayersItemStatic = sharedTypes & {
  label: string;
  option: LayersItemOptionsEnum;
};

type LayersItemDynamic = sharedTypes & {
  currentLayer: LayersElement;
};

export type LayersItemProps = (LayersItemDynamic | LayersItemStatic) & sharedTypes;

I am trying to use them like so:

export const LayersItem: FC<LayersItemProps> = (props): ReactElement => {
  const isHeader = props.option === LayersItemOptionsEnum.HEADER;

  const { isInEditMode } = useAppSelector((state) => state.editMode);

  const shouldRenderOptions = isInEditMode && !isHeader;

  const { selectedState } = useAppSelector((state) => state);
  const states = useAppSelector((state) => state.statesData.elements);

  return (
    <StyledLayersItem header={isHeader}>
      <Row>
        <Col span={8} offset={1 /* todo: add offset dynamically */}>
          <h1>{props.label ? props.label : props.currentLayer.name}</h1>
        </Col>
        <Col span={8} offset={4}>
          {shouldRenderOptions ? (
            <Form.Item className="form-item" initialValue={props.children}>
              <Select>
                {generateOptions({ selectedState, states, props.currentLayer }).map((value) => {
                  return (
                    <Select.Option value={value.id} key={value.id}>
                      {value.name}
                    </Select.Option>
                  );
                })}
              </Select>
            </Form.Item>
          ) : (
            <>{props.children}</>
          )}
        </Col>
      </Row>
    </StyledLayersItem>
  );
};

But I am getting the errors like this one:

Property 'label' does not exist on type 'PropsWithChildren<LayersItemProps>'.
  Property 'label' does not exist on type 'sharedTypes & { currentLayer: LayersElement; } & { children?: ReactNode; }'.

For each of the props. apart from props.children. Like it doesn't see the union in types. Or am I misunderstanding something?

Basically, if the props have label, or option, I want props to be of type LayersItemStatic & shared Types, and if there is currentLayer in props, I want them to be of type LayersItemDynamic & sharedTypes.

So what am I missing here?

I am trying to achieve something like this:

type SharedType = SharedDisplayAndEditTypes & {
  required?: boolean;
  validationMessage: string;
  name: string;
};

type TextType = {
  type: 'text';
  children: string;
};

type NumberType = {
  type: 'number';
  children: number;
};

type InputType = TextType | NumberType;

type DropdownType = {
  type: 'dropdown';
  options: string[];
  children: string;
};

type ColorType = {
  type: 'color';
  defaultValue: string;
};

export type DetailsItemEditProps = (DropdownType | InputType | ColorType) & SharedType;
Alex Ironside
  • 4,658
  • 11
  • 59
  • 119
  • 1
    Please either share reproducible example or provide the line where you have an error. If you have a union of objects, TS will allow you to use only common properties. If some property is specific for only one element of the union you should create your own custom typeguard in order to use this element property – captain-yossarian from Ukraine Sep 16 '21 at 12:12
  • This error is thrown in each place I call `props.something`. Apart from `props.children` – Alex Ironside Sep 16 '21 at 12:18

1 Answers1

2

Consider this example:

import { ReactElement } from 'react'

type LayersElement = {
    tag: 'LayersElement'
}

export enum LayersItemOptionsEnum {
    OPERATOR,
    HEADER,
}

type sharedTypes = {
    children: string | ReactElement;
};

type LayersItemStatic = sharedTypes & {
    label: string;
    option: LayersItemOptionsEnum;
};

type LayersItemDynamic = sharedTypes & {
    currentLayer: LayersElement;
};

export type LayersItemProps = (LayersItemDynamic | LayersItemStatic) & sharedTypes;

declare var props: LayersItemProps;

props.children // ok

Only children prop is allowed because it is a common prop for each element of the union.

See Best common type

Since nobody know which elem of the union is actually allowed TS decides to allow you only properties which are safe for each element of the union.

Consider this smaller example:

type LayersItemStatic = {
    label: string;
    option: string;
};

type LayersItemDynamic = {
    currentLayer: string;
};

export type LayersItemProps = LayersItemDynamic | LayersItemStatic

declare var props: LayersItemProps;

Because there are no common props, you are not allowed to use any prop.

I don't think that this type is correct:

export type LayersItemProps = (LayersItemDynamic | LayersItemStatic) & sharedTypes

Since LayersItemDynamic | LayersItemStatic is reduced to {} and LayersItemProps basically equals to sharedTypes.

Since you already added & sharedType to both LayersItemDynamic | LayersItemStatic you need rewrite your type LayersItemProps as follow:

import { ReactElement } from 'react'

type LayersElement = {
    tag: 'LayersElement'
}

export enum LayersItemOptionsEnum {
    OPERATOR,
    HEADER,
}

type sharedTypes = {
    children: string | ReactElement;
};

type LayersItemStatic = sharedTypes & {
    label: string;
    option: LayersItemOptionsEnum;
};

type LayersItemDynamic = sharedTypes & {
    currentLayer: LayersElement;
};

const hasProperty = <Obj, Prop extends string>(obj: Obj, prop: Prop)
    : obj is Obj & Record<Prop, unknown> =>
    Object.prototype.hasOwnProperty.call(obj, prop);


export type LayersItemProps = LayersItemDynamic | LayersItemStatic

const isDynamic = (props: LayersItemProps): props is LayersItemDynamic => hasProperty(props, 'currentLayer')
const isStatic = (props: LayersItemProps): props is LayersItemStatic => hasProperty(props, 'label')


declare var props: LayersItemProps;

if (isDynamic(props)) {
    props.currentLayer // ok
}

if (isStatic(props)) {
    props.label // ok
    props.option // ok

}

Playground

  • 1
    Ok, I see your point. Can you check the question? I will add a code snippet which does smth similar to what I'm trying to achieve, but in a simple way. Can you please tell me why it works there, but not in my case? The types only share one element, yet ts is able to detect which type is which based on this one element – Alex Ironside Sep 16 '21 at 12:41
  • 1
    Ok I was able to answer my own question by adding `type: 'static'` and `type: dynamic` to types. – Alex Ironside Sep 16 '21 at 12:45