#Auto-infer type from parts of an object for React props

12 messages · Page 1 of 1 (latest)

worthy owl
#

I can't figure out if what I'm trying to do is possible to do in Typescript.
I want to be able to infer certain properties from an object. Not the whole object itself. Let me give you an example.

Given this object:

export const widgetConfig: WidgetConfig = {
  greeting: {
    label: "Greeting",
    value: "Hello",
  },
  showTitle: {
    label: "Show Title",
    value: true,
  },
  // ...
};

I need a type that looks like this:

{
  greeting: string;
  showTitle: boolean;
  // ...
}

greeting comes from the key greeting in the widgetConfig. It's value type comes from the type of data stored within value. The same is reflected for the showTitle boolean. This way, I can utilize the type inside React component props:

const Example = (props: WidgetProps<typeof widgetConfig>) => {
  return props.showTitle && <h1>{props.greeting} from example</h1>;
};

The current problem I have is that both showTitle and greeting on props has the type string | number | boolean | undefined. I just want a string type for the greeting and a boolean type for the showTitle props.

Here is what WidgetProps looks like:

export type Widget =
  | BooleanWidget<boolean>
  | CodeWidget<string>
  | DateWidget<string>
  | NumberWidget<number>
  | SelectWidget<string>
  | TextWidget<string>;

export type WidgetConfig = Record<string, Widget>;

export type WidgetProps<T extends WidgetConfig> = {
  [K in keyof T]: T[K]["value"];
};

Any ideas how to auto-infer the types based off of the config?

proud iris
#

@worthy owl You can't annotate your type as WidgetConfig because TS will 'forget' the specific structure of the type.

#

You can use satisfies instead, which will validate the structure but won't override the specific structure:

export const widgetConfig = {
  greeting: {
    label: "Greeting",
    value: "Hello",
  },
  showTitle: {
    label: "Show Title",
    value: true,
  },
  // ...
} satisfies WidgetConfig;
worthy owl
#

Ah interesting. That did help a lot, thanks! I do get for the showTitle a type of true, not a boolean. Can this be fixed using satisfies?

#

[K in keyof T]: typeof T[K]["value"]; does not work the way I want it too

proud iris
#

Can you give an example that reproduces it? The definition of BooleanWidget<boolean> is probably relevant?

worthy owl
#
export const widgetConfig = {
  greeting: {
    label: "Greeting",
    value: "Hello",
  },
  showTitle: {
    label: "Show Title",
    value: true,
  },
} satisfies WidgetConfig;

export default function Example(props: WidgetProps<typeof widgetConfig>) {
  return props.showTitle && <h1>{props.greeting} from example</h1>; // props.showTitle -> true; props.greeting -> string;
}

Instead of props.showTitle -> true/false I want props.showTitle -> boolean

And as to your input:

interface BaseWidget<T> {
  label?: string;
  description?: string;
  required?: boolean;
  value?: T;
}

export interface BooleanWidget<T> extends BaseWidget<T> {}
worthy owl
#

Let me know if you can't reproduce it @proud iris

proud iris
#

Yeah, not sure why satisfies is causing value to retain it's specific true/false value rather than just being boolean.

#

I guess that's something you can specifically handle in the WidgetProps layer:

#
export type WidgetProps<T extends WidgetConfig> = {
  [K in keyof T]: WidenBoolean<T[K]["value"]>;
};

// Prevent Widget props from inferring as specific true/false values
type WidenBoolean<T> = T extends boolean ? boolean : T;
worthy owl
#

That did do the trick, thanks a ton for your help!