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

Using RxJs in Angular, you can invoke an observable from within an error callback

I have a scenario where two observable calls are dependent on each other. Everything works fine, but when an error occurs in the response, I need to trigger another observable to rollback the transaction. Below is my code: return this.myService.createOrde ...

Challenges of Integrating Auth0 with Angular2/.NETCore Project

I am struggling to integrate this Custom Login auth0 service because I am facing issues with the imports. The problem arises specifically with the usage of declare var auth0: any. Every time I attempt to use it, I encounter the following error: EXCEPTION: ...

Unable to directly assign a variable within the subscribe() function

My goal is to fetch a single row from the database and display its information on my webpage. However, I've encountered an issue with the asynchronous nature of subscription, which prevents immediate execution and access to the data. Upon running the ...

Can we access global variables directly in an Angular 2 HTML template?

After setting the app.settings as shown below public static get DateFormat(): string { return 'MM/DD/YYYY';} I need to utilize it in one of my HTML templates for a component. This is what I want to achieve. <input [(ngModel)]="Holiday" [dat ...

AgGrid Encounters Difficulty in Recovering Original Grid Information

After making an initial API call, I populate the grid with data. One of the fields that is editable is the Price cell. If I edit a Price cell and then click the Restore button, the original dataset is restored. However, if I edit a Price cell again, the ...

Employing Class Categories in Static Procedures

I am currently working on developing a foundational Model that will serve as the base for a specific model class, which will have an interface detailing its attributes. Within the base Model class, I am aiming to incorporate a static factory() function th ...

Executing MongoDB collection operations with array filtering

I am looking to count records based on tags and filter them before including in specific groups // data in database {tags: ['video', 'Alex'], ... }, {tags: ['video', 'John'], ... }, {tags: ['video', 'J ...

Utilizing AWS CDK to Define StackProps Input Variables

Recently, I have started using the AWS CDK and encountered a challenge. I want to allow end users to define custom input variables when using my AWS CDK without having to edit the entire code. While I have been able to work with standard types such as stri ...

Perplexed by the implementation of "require(...)" in TypeScript

After reading several blog posts, my understanding of TypeScript modules remains confusing. I have worked with three different modules (all installed via npm) and encountered varying behavior: (1) Importing and using Angular 2 from npm required me to add ...

Guide on navigating an array of objects using the provided keys as a starting point in Javascript/Typescript

Assuming I have an array of objects structured like this: const events: Array<{year: number, month: number, date: number}> = [ {year: 2020, month: 10, date: 13}, {year: 2021: month: 3, date: 12}, {year: 2021: month: 9, date: 6}, {year: 2021: mont ...

What strategies can be employed to create a system for managing multiple permission groups in MongoDB effectively?

Currently, I am tackling a complex project management application which involves the challenge of setting up resource permissions for various user profiles. The task at hand: User scenario Meet John, a user with a standard user profile. John initiates a ...

In Next.js, the Typescript compiler does not halt when an error occurs

I am looking to incorporate Next.js with TypeScript into my project. I followed the official method of adding TypeScript to Next.js using npx create-next-app --typescript. Everything seemed fine, but when a TypeScript error occurs (e.g. const st: string = ...

What could be causing the Angular router outlet to not route properly?

Check out this demo showcasing 2 outlets (Defined in app.module.ts): <router-outlet></router-outlet> <router-outlet name="b"></router-outlet> The specified routes are: const routes: Routes = [ { path: 'a', com ...

When importing a module, the function in the ts file may not be recognized or located

While attempting to create a VSTS (Azure Devops) Extension, I encountered a perplexing issue. Within my HTML page, I have a button element with an onclick listener: <!DOCTYPE html> <head> <script type="text/javascript"> VS ...

What is the best way to send a string parameter from an Angular UI to a Node.js backend?

My goal is to transfer a string value from an Angular UI to a Node.js backend API, which will then search in MongoDB using the provided string value as shown below. I am attempting to receive input in enteredValue and pass it on to the http.get call as pa ...

How can I convert duplicate code into a function in JavaScript?

I have successfully bound values to a view in my code, but I am concerned about the duplicate nested forEach loops that are currently present. I anticipate that Sonarcube will flag this as redundant code. Can anyone advise me on how to refactor this to avo ...

Encountering intellisense problems while declaring or utilizing TypeScript types/interfaces within Vue.js Single File Component (SFC) documents

For my Nuxt 3 project, I am developing a component and attempting to declare an interface for the component props to ensure strong typings. Here is an example: <script setup> interface Props { float: number; } const props = defineProps<Props> ...

Categorize messages based on the date they were last read in Angular

I am looking to organize my chat application messages by date, similar to the layout in Microsoft Teams app. Here is an example of the message data: [ { "id": 577, "source": { "userID": 56469, ...

Enhancing a prototype instance in TypeScript while activating strict mode

When working with an instance named remote from a factory in a vendor script, I encountered the need to add my own methods and members to that instance. While seeking a solution, I came across an insightful response on extending this in a Typescript class ...

Utilize the power of relative import by including the complete filename

When working on my TypeScript project, I have noticed that to import ./foo/index.ts, there are four different ways to do it: import Foo from './foo' // ❌ import Foo from './foo/index' // ❌ import Foo from './foo/i ...