1

I'm trying to implement something similar to storybook's "Controls" feature where you can define a bunch of properties, and they become controls in the UI.

I defined a schema type and a schema of how to create those controls:

// Example schema
var newSchema: BlockSchema = {
  title: "New Schema",
  controls: {
    name: {
      type: 'string',
      placeholder: 'Please insert your name'
    },
    size: {
      type: 'select',
      options: ['quarter', 'half', 'full']
    },
    hasInfo: {
      type: 'bool'
    },
    amount: {
      type: 'number'
    }
  }
}

But now I need a type that is the result of what the user has selected. A type for the final values, something like:

type MapControlTypes = {
  bool: boolean;
  string: string;
  select: string;
  number: number;
};

type InferType<T extends BlockSchema> = { /* MapControlTypes<?????????> */ }

type NewSchemaControls = InferType<typeof newSchema>;

/* Expected result:
  NewSchemaControls = {
    name: string;
    size: string;
    hasInfo: boolean;
    amount: number;
  }
*/

I need to infer the types from the controls property of my schema, but how could I implement this inference? Here's a playground with complete example code


I tried implementing this, and this solution. But they don't work well and also only support two types.

Titian Cernicova-Dragomir's solution didn't work too. Playground, but it has a very similar problem that happened when I tried other solutions. Maybe is it because I'm not using MapControlTypes on my ControlSchema?


Solved!

assisrMatheus
  • 445
  • 1
  • 5
  • 17

1 Answers1

2

You can do this, using a mapped type, but first you need to preserve the original type of the schema. If you add a type annotation to it, then information about specific fields and types will be lost. It will just be a BlockSchema

The easiest way to do this is to omit the annotation, and use an as const assertion to make the compiler infer literal types for type.

With this extra info in hand, we can then use a mapped type to transform the schema into an object type:

type InferType<T extends BlockSchema> = {
  -readonly [P in keyof T['controls']]: MapControlTypes[T['controls'][P]['type']]
}

Playground Link

You can also use a function to create the schema, and be more selective about what gets the readonly treatment:

function buildBlockSchema<B extends BlockSchema>(b: B) {
  return b
}

Playground Link

Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
  • It didn't fully work(edited the post with the playground url), unfortunately. But curiously, it has a very similar problem that happened when I tried other solutions. Maybe is it because I'm not using `MapControlTypes` on my `ControlSchema`? I saw that the complete example playground link was broken, and fixed those too. If it helps. – assisrMatheus Apr 07 '22 at 16:58
  • Oh wait. No I understand what you mean by removing type anotation and using const. Let me try something. Will keep you posted – assisrMatheus Apr 07 '22 at 17:04
  • No success. I actually need `BlockSchema` because it helps defining what type exists when writing the schema itself, and what fields are available for a type. (string type has placeholder, where select has options). And If we remove the annotation. We lose the autocomplete in there. So maybe what I want is not possible? – assisrMatheus Apr 07 '22 at 17:13
  • Solved by using the function approach! – assisrMatheus Apr 07 '22 at 17:31
  • 1
    @assisrMatheus Sorry, was offline. glad it worked out – Titian Cernicova-Dragomir Apr 07 '22 at 21:00