Creating generic output types in TypeScript based on the input types

In my React-Native project, I'm focusing on implementing autocomplete and type validation. One of the challenges I'm facing is configuring the types for the stylesheet library I am using.

A customized stylesheet is structured like this:

const styles = createStyles({
  variable1: 100,
  variable2: "10rem",

  header: {
    width: "100%",
    height: 40,
    alignContent: "center",
    flex: "$flexAll",
    margin: "$gapMD"
  }
})

Each style value in the defined styles should not only accept its original type but also a string, function, etc.

After processing the stylesheet, the output is a standard React-Native stylesheet. Therefore, the function result should retain the same properties as the input, mapping them to the original style types.

For example, the property flex should be strictly a number, not a ambiguous union like

number | string | function | etc.

This is the current progress:

import { ImageStyle, TextStyle, ViewStyle } from "react-native"
import EStyleSheet from "react-native-extended-stylesheet"

type Function<K> = () => K

type AllStyles = ImageStyle & TextStyle & ViewStyle
type StyleSet<T> = { [P in keyof T]: AllStyles }

type EValue<T> = T | string & {}
type EVariable<K> = EValue<K> | Function<EValue<K>>
type EStyle<T> = { [P in keyof T]: EVariable<T[P]> }

type EAnyStyle = EStyle<ImageStyle> | EStyle<TextStyle> | EStyle<ViewStyle>
type EStyleSet<T> = { [P in keyof T]: number | string | EAnyStyle | EStyleSet<T> }

export const createStyles = <T>(styles: EStyleSet<T>) =>
                            EStyleSheet.create(styles) as StyleSet<T>

Unfortunately, the autocomplete feature is not fully functional, and I sense that my definitions are becoming overly complicated. Moreover, the resulting type is not entirely accurate.

If there's a TypeScript expert out there who can assist me in resolving this issue, it would be greatly appreciated.

I've created a Sandbox where you can test some of the types:
https://codesandbox.io/s/typescript-style-mania-h62cv

Answer №1

please confirm if this is the correct approach:

import {FlexStyle, ImageStyle, TextStyle, ViewStyle} from './react-native';

///////////////////////////////////////////////////////
// MOCK

const EStyleSheet = { create: obj => obj };

///////////////////////////////////////////////////////
// TYPES

// using ts-essentials for types.
type Primitive = string | number | boolean | bigint | symbol | undefined | null;
type Builtin = Primitive | Function | Date | Error | RegExp;
type ExtendTypes<T, E> = T extends Builtin
    ? T | E
    : T extends Map<infer K, infer V>
        ? Map<ExtendTypes<K, E>, ExtendTypes<V, E>>
        : T extends ReadonlyMap<infer K, infer V>
            ? ReadonlyMap<K, ExtendTypes<V, E>>
            : T extends WeakMap<infer K, infer V>
                ? WeakMap<K, ExtendTypes<V, E>>
                : T extends Set<infer U>
                    ? Set<ExtendTypes<U, E>>
                    : T extends ReadonlySet<infer U>
                        ? ReadonlySet<ExtendTypes<U, E>>
                        : T extends WeakSet<infer U>
                            ? WeakSet<ExtendTypes<U, E>>
                            : T extends Array<infer U>
                                ? Array<ExtendTypes<U, E>>
                                : T extends Promise<infer U>
                                    ? Promise<ExtendTypes<U, E>>
                                    : T extends {}
                                        ? { [K in keyof T]: ExtendTypes<T[K], E> }
                                        : T;

type AllStyles = ImageStyle & TextStyle & ViewStyle;
type StyleSet<T> = Pick<{
  header: ViewStyle;
  font: TextStyle;
}, Extract<'header' | 'font', keyof T>>;

const createStyles = <T extends {
  // Defining properties precisely here.
  // header?: ExtendTypes<ViewStyle, Function | String>;
  // font?: ExtendTypes<TextStyle, Function | String>;
  [key: string]: string | number | Function | ExtendTypes<AllStyles, Function | String>; // Keeping string unions capitalized.
}>(styles: T): StyleSet<T> =>
  EStyleSheet.create(styles);

///////////////////////////////////////////////////////
// TEST

const styles = createStyles({
  variable1: 100,
  variable2: "10rem",

  header: {
    width: "100%",
    height: 40,
    alignItems: "flex-start",
    alignSelf: "$alignSelf", // autocomplete, allowing custom values
    flex: "$flexAll",
    margin: "$gapMD",
    // autocomplete should work here, but doesn't
    // now it works
  },
  font: {
    fontSize: 20
  }
});

const imageStyle: ImageStyle = {
  alignItems: "center"
};

// Valid
console.log("header", styles.header);
console.log("header.fontSize", styles.font.fontSize);
console.log("imageStyle.alignItems", imageStyle.alignItems);

// Invalid: ViewStyle doesn't have textTransform
// now it works
console.log("header.textTransform", styles.header.textTransform);

// Invalid: TextStyle doesn't have resizeMode
// now it works
console.log("font.resizeMode", styles.font.resizeMode);

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What could be causing NgModel to fail with mat-checkbox and radio buttons in Angular?

I am working with an array of booleans representing week days to determine which day is selected: selectedWeekDays: boolean[] = [true,true,true,true,true,true]; In my HTML file: <section> <h4>Choose your days:</h4> <mat-che ...

Unable to access data from the Array by passing the index as an argument to the method

Having trouble retrieving an item from an Array using method() with an index argument that returns undefined export class DataService { public list = [ { id: 11, name: 'Mr. Nice' }, { id: 12, name: 'Narco' }, ...

Setting up the 'nativescript-stripe' plugin in your NativeScript Vue project for seamless integration

Trying to integrate the «nativescript-stripe» plugin into my Nativescript Vue app has been a challenge. The documentation and demos on the plugin's GitHub are geared towards Angular and TypeScript, making it difficult to adapt for Vue. Can anyone pr ...

TS2307 error encountered in Angular 2 TypeScript due to the inability to locate a module for a private npm

I've been working on creating some components for internal company use, with the intention of sharing them through our private npm repository. However, I've hit a roadblock while trying to add these components to an app using npm and systemjs - I ...

Looping Through RxJS to Generate Observables

I am facing the challenge of creating Observables in a loop and waiting for all of them to be finished. for (let slaveslot of this.fromBusDeletedSlaveslots) { this.patchSlave({ Id: slaveslot.Id, ...

Using next.js with GraphQL resulted in the following error: "Invariant Violation: Unable to locate the "client" in the context or passed in as an option..."

I've searched extensively online, but I can't find a solution to my problem. Here is the error message I'm encountering: Invariant Violation: Could not find "client" in the context or passed in as an option. Wrap the root component in an ...

Converting input dates in nest.js using TypeScript formatting

Is it possible to set a custom date format for input in nest.js API request body? For example, like this: 12.12.2022 @ApiProperty({ example: 'ADMIN', description: 'Role name', }) readonly value: string; @ApiProperty({ ...

An error occurred suddenly while building: ReferenceError - the term "exports" is not defined in the ES module scope

Having trouble resolving this error in the Qwik framework while building a static site: ReferenceError: exports is not defined in ES module scope at file:///media/oem/MyFiles/8_DEVELOPMENT/nexasoft/server/@qwik-city-plan.mjs:1:1097 at ModuleJob. ...

Ensuring type safety at runtime in TypeScript

While delving into the concept of type safety in Typescript, I encountered an interesting scenario involving the following function: function test(x: number){ console.log(typeof x); } When calling this method as test('1'), a compile time er ...

Develop a FormGroup through the implementation of a reusable component structure

I am in need of creating multiple FormGroups with the same definition. To achieve this, I have set up a constant variable with the following structure: export const predefinedFormGroup = { 'field1': new FormControl(null, [Validators.required]) ...

Creating a welcome screen that is displayed only the first time the app is opened

Within my application, I envisioned a startup/welcome page that users would encounter when the app is launched. They could then proceed by clicking a button to navigate to the login screen and continue using the app. Subsequently, each time the user revisi ...

Save Component Characteristics in a type-safe array

Is it possible in Svelte to define a strongly typed array that matches the properties exported by a specific component? For instance, if I have the following code snippet, const things = [], is there a way for Svelte to recognize that each item within the ...

employing a parameterized type to accommodate a combination of two diverse items as input

I am facing a challenge with a simple use case and have been unable to find an example that covers it adequately. The situation is this: I have a function that should accept two different objects that are very similar. Therefore, I want to use a generic t ...

Tips for showcasing with [displayWith] in Material2's AutoComplete feature

My API returns an array and I am using Material2#AutoComplete to filter it. While it is working, I am facing an issue where I need to display a different property instead of the currently binded value in the option element. I understand that I need to uti ...

How to retrieve a variable from an object within an array using AngularJS code

I recently started learning TypeScript and AngularJS, and I've created a new class like the following: [*.ts] export class Test{ test: string; constructor(foo: string){ this.test = foo; } } Now, I want to create multiple in ...

Struggling to map a JSON file in a NextJS project using Typescript

I'm a beginner in JS and I'm currently struggling to figure out how to display the data from a json file as HTML elements. Whenever I run my code on the development server, I encounter the error message: "props.books.map is not a function&q ...

How come my uploaded Excel Javascript add-on opens in an external browser instead of the task pane?

Note: It has come to my attention that I must save the taskpane.html file on my local drive before it opens in an external browser. This detail slipped my notice last week. I am currently developing a Javascript, or rather Typescript, API add-in for Excel ...

Dragging element position updated

After implementing a ngFor loop in my component to render multiple CdkDrag elements from an array, I encountered the issue of their positions updating when deleting one element and splicing the array. Is there a way to prevent this unwanted position update ...

What is the process for validating observations with an observer confirmation?

Can you explain what the of() function creates in this scenario and how it operates? public onRemoving(tag): Observable<any> { const confirm = window.confirm('Do you really want to remove this tag?'); return Observable.of(tag).fil ...

How can one use an Angular Route to navigate to a distinct URL? Essentially, how does one disable matching in the process?

I'm working on a front-end Angular application and I need to add a menu item that links to an external website. For example, let's say my current website has this URL: And I want the menu item in my app to lead to a completely different website ...