0

I am using React.CSSProperties for creating css variables so I can create like

let css: React.CSSProperties = {
  position: "relative",
  display: "inline-block",
  background: "red"
}

I am trying to create a theme which will have css variables at the root level or nested inside modifiers like size(sm,md,lg) or variant(light,dark) or other as below

let theme: MyTheme = {
  background: "red",       // css
  color: "white",          // css  

  button: {                // custom: component name Level1
    background: "red",     // css
    color: "white",        // css

    light: {               // custom: modifier variant Level2
      background: "red",   // css
      color: "white",      // css

      hollow: {            // custom: modifier Level3
        borderColor: "red",
        color: "red",
        background: "transparent",
      }
    }
    dark: {
      background: "red",
      color: "white",

      hollow: {
        borderColor: "black",
        color: "black",
        background: "transparent",

        modfierLevel4: {
          some: "css"

          modfierLevel5: {
            some: "css"

            modfierLevel6: {
              some: "css"
            }
          }
        }
      }
    }
  }
}

Basically I am looking for recursive object type in typescript like below, but ending up in circular reference?

type Modifiers = 'button' | 'dark' | 'light' | 'otherModifiers'

interface Theme extends React.CSSProperties {
 [k in Modifiers]?: Theme;
}

I am able to find the answer (below) close to it.

type Theme = React.CSSProperties & { [keys in Modifiers]?: Theme }

But facing some issue. If modifier name is given invalid like "btn" instead of "button"

  • error is thrown properly at level 1 (as expected)
  • but no error is thrown from level 2 and above. (but expected to throw error)

How should I create types for the above?

0

1 Answer 1

2

Assuming you are using TS3.4 or below:

This is the result of a known issue in which excess property checks are not performed on nested intersection types like Theme. Technically speaking, types in TypeScript are generally "open" or "extendable", meaning it's okay for a value of a type to have more properties than mentioned in its type definition. You rely on this with things like interface A { a: string } and interface B extends A { b: string }, where a value of type B is also a value of type A.

But usually when people are using object literals like you are, it's a mistake to add extra properties... especially in your case where the "extra" property is just a typographical error for an optional property. So the compiler decides that when you're assigning a new object literal to a type, extra properties are an error. That is, types of fresh object literals are "closed" or "exact"...

...well, or they should be, except for this bug with nested intersections. In TypeScript 3.4 or below, this is the behavior:

type Foo = { baseProp: string, recursiveProp?: Foo };
const foo: Foo = {
    baseProp: "a",
    recursiveProp: {
        baseProp: "b",
        extraProp: "error as expected" // error 👍
    }
}

type Bar = { baseProp: string } & { recursiveProp?: Bar };
const bar: Bar = {
    baseProp: "a",
    recursiveProp: {
        baseProp: "b",
        extraProp: "no error?!" // no error in TS3.4 😕
    }
}

Playground link

Luckily for you, this error has been fixed for TypeScript 3.5, which should be released on May 30, 2019, or you could try out the nightly build of TypeScript via typescript@next

If you can't wait until then or can't upgrade soon, you can work around this by using a mapped type to replace the intersection with a more normal-looking object:

type Id<T> = { [K in keyof T]: T[K] };
type Bar = Id<{ baseProp: string } & { recursiveProp?: Bar }>;
const bar: Bar = {
    baseProp: "a",
    recursiveProp: {
        baseProp: "b",
        recursiveProp: {
            baseProp: "c",
            extraProp: "error yay" 👍
        }
    }
}

Playground link

The expected error shows up, and all is right with the world. Hope that helps; good luck!

Sign up to request clarification or add additional context in comments.

2 Comments

This works fine if the type "Modifier" is well defined. But instead if create like type Modifiers = 'dark' | 'light' | string;, then I am facing error. Is there any way I can create nested type for any custom keys?
type Modifiers = 'dark' | 'light' | string is the same as type Modifiers = string. So at that point you're just using an index signature and you need to make sure that the value type conforms to the value types in React.CSSProperties. This seems like a separate question requiring its own code and specific errors, etc, for someone to answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.