I have a typescript interface like so:
interface DataSku {
fields: {
sku: string;
}
}
interface PostProduct {
fields: {
title: string;
skus: DataSku[];
}
}
Now I want to extend the Product interface so that each object in the skus array has an extra field. So I tried this:
interface Sku extends DataSku {
stripe: Stripe.skus.ISku;
}
interface Product extends PostProduct {
fields: PostProduct['fields'] & {
skus: Sku[];
}
}
In my code, I try a loop like so:
(product as Product).fields.skus.forEach(sku => console.log(sku.stripe));
This throws the following Typescript error:
Property 'stripe' does not exist on type 'DataSku'.
Have I extended the interface wrong? The console does output my stripe object as expected, so it's just Typescript that isn't happy with the definition.
A much more elegant (and working) approach for this would be to use generics.
Update PostProduct with a generic parameter that's used for the type of fields.skus:
interface PostProduct<T extends DataSku = DataSku> {
fields: {
title: string;
skus: T[];
}
}
T extends DataSku means the type has to be a subtype of DataSku. And we can even set a default value for T so PostProduct can also be used without having to specify the generic parameter.
Now, for Product we just have to pass Sku as generic parameter:
interface Product extends PostProduct<Sku> {}
Playground
That said, if you want to it without generics and without modifying DataSku and PostProduct. You could do this:
interface Product extends PostProduct {
fields: Omit<PostProduct['fields'], 'skus'> & {
skus: Sku[];
}
}
Playground
Related
I have following Nestjs DTO class:
// create-job-offer.dto.ts
import { IsOptional, IsNumber } from 'class-validator';
export class CreateJobOfferDto {
#IsNumber()
#IsOptional()
mentorId: number;
#IsNumber()
#IsOptional()
companyId: number;
}
I want to obtain the list of class properties: ['mentorId', 'companyId'].
I tried so far in a controller without success following methods:
Object.getOwnPropertyNames(new CreateJobOfferDto());
Object.getOwnPropertyNames(CreateJobOfferDto);
Object.getOwnPropertySymbols(new CreateJobOfferDto());
Object.getOwnPropertySymbols(CreateJobOfferDto);
Object.getOwnPropertyDescriptors(CreateJobOfferDto);
Object.getOwnPropertyDescriptors(new CreateJobOfferDto());
Object.getPrototypeOf(CreateJobOfferDto);
Object.getPrototypeOf(new CreateJobOfferDto());
If I add a method, or vars in a constructor, I can get them, but not the properties.
The reason why I want to achieve this is, I am using Prisma and React, and in my React app I want to receive the list of class properties so that I can generate a model form dynamically.
There is no easy direct way to get the list of properties of a DTO class, or
more comprehensively, any type of class that has only properties.
The reason is that whenever you defined a property without any values, it becomes disappears after compiling to javascript.
Example:
// typescript
class A {
private readonly property1: string;
public readonly property2: boolean;
}
it compiles to this:
// javascript
"use strict";
class A {}
In order to achieve this goal, you need to write a custom decorator. like this:
const properties = Symbol('properties');
// This decorator will be called for each property, and it stores the property name in an object.
export const Property = () => {
return (obj: any, propertyName: string) => {
(obj[properties] || (obj[properties] = [])).push(propertyName);
};
};
// This is a function to retrieve the list of properties for a class
export function getProperties(obj: any): [] {
return obj.prototype[properties];
}
Imagine I have a DTO class for a user, like this:
import { getProperties } from './decorators/property.decorator';
export class UserDto {
#Property()
#IsNotEmpty()
firstName: string;
#Property()
#IsEmail()
#IsOptional()
public readonly email: string;
}
and I want to get all properties in a list, so we need to call the getProperties method which we defined earlier, so:
import { UserDto } from './dtos/user.dto';
getProperties(UserDto); // [ 'firstName', 'email' ]
You can also use an npm module ts-transformer-keys.
A TypeScript custom transformer which enables to obtain keys of given type.
How to use:
import { keys } from 'ts-transformer-keys';
interface Props {
id: string;
name: string;
age: number;
}
const keysOfProps = keys<Props>();
console.log(keysOfProps); // ['id', 'name', 'age']
I need a React prop to deal with all possible html attributes of an HTML div element part of a React component, but I'm having an issue with Typescript strictness vs React possibilities.
Here the component:
import React from 'react'
type DivAttrs = {
container?: React.HTMLAttributes<HTMLDivElement>
}
...
<div {...divAttributes?.container}>
And here the prop const provided to the component:
const divAttributes: DivAttrs = {
container: {
'aria-describedby': 'test',
'data-custom-attribute': 'test',
'data-random-attribute': 'test',
id: 'test'
}
}
The props data-custom-attribute and data-random-attribute give these errors
(property) 'data-custom-attribute': string
Type '{ 'aria-describedby': string; 'data-custom-attribute': string; 'data-random-attribute': string; id: string; }' is not assignable to type 'HTMLAttributes<HTMLDivElement>'.
Object literal may only specify known properties, and ''data-custom-attribute'' does not exist in type 'HTMLAttributes<HTMLDivElement>'.(2322)
What would be the perfect solution to fix this issue? Thanks a lot
Update for TypeScript 4.1+:
The introduction of Template Literals allows us to create a type that accepts both HTMLAttributes and custom data-* attributes:
type DivAttrs = {
container?: React.HTMLAttributes<HTMLDivElement> & {[dataAttibute: `data-${string}`]: string}
}
Previous solution
The data-custom-attribute and data-random-attribute properties do not exist in the React.HTMLAttributes type or any pre-existing type, hence your best bet would be to combine the existing React.HTMLAttributes type (to still get access to common HTMLDivElement element attributes) with your own CustomAttrs:
interface CustomAttrs {
'data-custom-attribute': string;
'data-random-attribute': string;
}
type DivAttrs = {
container?: React.HTMLAttributes<HTMLDivElement> & CustomAttrs,
}
we can add an index signature into the prop’s type.
type DivAttrs = {
container?: React.HTMLAttributes<HTMLDivElement> & { [x: string]: any},
}
Language=Typescript
I want to use the aggregation of 2 interfaces as the value of an indexable-type in a 3rd interface.
Interface 1:
export interface Employee {
id: string
name: string
}
Interface 2:
export interface Department {
department: string
}
Now I want to write an interface equivalent of this:
export interface EmployeeDetails {
employees: {
[key: string]: {
employeeDetails: EmployeeWithDepartment
}
}
}
where EmployeeWithDepartment is:
export interface EmployeeWithDepartment extends Employee {
departmentDetails: Department
}
Is there a way I can create the EmployeeDetails interface without actually creating EmployeeWithDepartment? Some way to include both Employee and Department at once in the EmployeeDetails interface?
PS: I've been using JS & TypeScript only for a week now, so I may not be aware of some concepts that can easily accomplish this.
I believe that what you are looking for is a type intersection, the & opertator. It combines all properties of two types.
For example:
interface A { a: number }
interface B = { b: string }
type C = A & B // { a: number, b: string }
To use that here in your types, you could do something like:
export interface EmployeeDetails {
employees: {
[key: string]: {
employeeDetails: Employee & { departmentDetails: Department }
}
}
}
Playground
This is probably a good page to read: https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#interfaces
I have an abstract class with default state values that are typed inside ResourceState interface. I am getting a following error when I try to assign my extended State to the state property of my abstract class. I tried many different ways using intersection, though it still doesn't work. What I would like to achieve is, that I can provide a generic interface to the state property so that I can add more values apart from the default ones.
This is the error I got:
Type '{ items: never[]; isLoading: false; error: string; }' is not assignable to type 'State'.
'{ items: never[]; isLoading: false; error: string; }' is assignable to the constraint of type 'State', but 'State' could be instantiated with a different subtype of constraint 'ResourceState'.
This is my code:
export interface BaseItem {
id: string;
name: string;
}
export interface ResourceState < Item extends BaseItem > {
items: Item[];
selectedItem ? : Item;
isLoading: boolean;
error: string;
}
export abstract class Resource < Item extends BaseItem, State extends ResourceState < Item > , Mutations, Getters, Actions > {
state: State = { // this is where the error occurs
items: [],
isLoading: false,
error: '',
}
}
When you say State extends ResourceState<Item> you are saying that State needs to conform to ResourceState<Item> but because this is a generic type parameter, it may not be exactly a ResourceState<Item>.
Imagine this:
interface MyState extends ResourceState<{ a: 123 }> {
myCustomRequiredProperty: number
}
Now you pass MyState as State to your class, then you contruct state with:
state: State = { // this is where the error occurs
items: [],
isLoading: false,
error: '',
}
Then your state variable would not be constructed with the myCustomRequiredProperty property, which is required.
This is the error that typescript is (cryptically) trying telling you.
If you are constructing the state variable in the Resource class, then the State should not be generic because your executable code in this class can't know all properties of the types that could be generically passed in. So State should not extend ResourceState<Item>, it should be exactly a ResourceState<Item>.
That would look like this:
export abstract class Resource<Item extends BaseItem> {
state: ResourceState<Item> = {
items: [],
isLoading: false,
error: '',
}
}
Playground
However, if you want the ability to add properties to state, then you can't do that without some initial values. You need to somehow initialize those unknown properties. To do that, you would use your approach above, but with a constructor that accepts whatever type is the full state and then fill in the unknown properties of that type with an object that has that info.
export abstract class Resource<
Item extends BaseItem,
State extends ResourceState<Item>
> {
state: State
constructor(initialState: State) {
this.state = {
...initialState, // Fills in extended properties
items: [],
isLoading: false,
error: '',
}
}
}
Playground
I'm expecting React components like so:
class MySetting extends React.Component<{}> {
static name = 'My Setting';
render() {
return (<div>{'setting here'}</div>);
}
}
class Widget extends React.Component<{}> {
static settings = [ MySetting ]
render() {
return (<div>{'widget here'}</div>);
}
}
And I'd like to make a flow definition for them that essentially mean:
A react component that has a static field name
A react component that has a static field settings which is of the above type
How do I do this? I've tried a couple things like:
interface Settings {
name: string;
}
export type SettingsComponent = React.ComponentType<Object> & Settings
interface Widget {
settings: Array<SettingComponent>;
}
export type WidgetComponent = React.ComponentType<Object> & Widget
Or
declare class SettingsComponent<Props, State = void> extends React$Component<Props, State> {
static name: string;
}
declare class WidgetComponent<Props, State = void> extends React$Component<Props, State> {
static settings: Array<Class<SettingsComponent<any, any>>>
}
But they inevitably throw various flow errors that are awkward to decipher. Is there a generally accepted way of doing this?
Looks like with some more research I found a solution (using flow-bin#0.63.1).
The answer is flow's $Subtype<T> utility type:
export type SettingsComponent = {
name: string;
} & $Subtype<React.ComponentType<Object>>
export type VisualizationComponent = {
settings: Array<SettingsComponent>;
} & $Subtype<React.ComponentType<Object>>
Though I'll add that while my previous flow errors are gone and errors do show up when using a component with missing static fields or accessing non-existent fields, my editor (Atom + Nuclide) doesn't show tooltips for the type anymore.