How to dynamically extend the interface or type of an object in TypeScript?

503 Views Asked by At

I am trying to use PayPal's Glamorous CSS-in-JS library in a boilerplate project I'm building which also leverages TypeScript.

Glamorous allows you to add props to an element like-so:

const Section = glamorous.section([
  {
    ...styles
  },
  ({size}: any){
    if(size === 'big') return {fontSize: '48px';}
    else if(size === 'small') return {fontSize: '12px';}
  },
  ({alignment}: any){
    if(alignment === 'left') return {textAlign: 'left'}
    else if(alignment === 'right') return {textAlign: 'right'}
  }
]);

So that you can then use the element in JSX like this:

<Section size="big"></Section>

I have a number of props like this that I would like to add to all of my Glamorous-generated elements. So, I've created a helper function that looks like this:

export const special = (glam: any, styles: object[]): GBPComp =>{
  return glam([styles as CSSStyleDeclaration[], specialProps]);
};

Where specialProps represents a function (like the one above) that adds the various props to my array of style declarations.

I intend to use it like this:

const Section = special(glamorous.section, [
  {
    ...styles
  }
]);

I then want all of the props provided by specialProps to be typed for my JSX usage. So, I've tried to create my GBPComp type like this:

export type GBPComp = React.StatelessComponent<CSSProperties&ExtraGlamorousProps&React.HTMLProps<HTMLElement>&{
  layout?: string,
  width?: string
}>;

The issue arises with the fact that not all of the elements returned will necessarily have a property of React.HTMLProps<HTMLElement>. They may be HTMLAnchorElement or HTMLTableElement, etc.

How might I be able to generate my GBPComp type dynmically, such that it adds those props (i.e. layout and width) to all returned types? Such that the following will work properly within TypeScript:

const Section = special(glamorous.section, [
  {
    ...styles
  }
]);

const Anchor = special(glamorous.a, [
  {
    ...styles
  }
]);

<a href="#" layout="someString">This is an Anchor</a>

<section layout="someString">This is a Section</section>
1

There are 1 best solutions below

1
On

If you do not want React.HTMLProps<HTMLElement> for every elements, instead of hardcoding React.HTMLProps<HTMLElement>, you could use generics for your GBPComp like so

export type GBPComp<ExtraProps> = React.StatelessComponent<React.CSSProperties & ExtraGlamorousProps & ExtraProps & { 
  layout: string,
  width: string
}>;

And special() also accepts ExtraProps as generics and pass to GBPComp like so

export function special<ExtraProps>(glam: any, styles: object[], ...specialProps): GBPComp<ExtraProps> {
  return glam([styles as CSSStyleDeclaration[], ...specialProps]);
};

Now you can assign React.HTMLProps<HTMLAnchorElement> props to your Anchor

const Anchor = special<React.HTMLProps<HTMLAnchorElement>>(glamorous.a, [
  {
    ...styles
  }
]);