TransWikia.com

TypeScript: cannot use variable to reference a string when constructing a type

Stack Overflow Asked by Joji on September 11, 2020

I am trying to type my action. Here is the code.

type Increment = {
  type: 'INCREMENT'
  payload: number
}

const action: Increment = {
  type: 'INCREMENT',
  payload: 1
}

console.log(action);

Then I wanted to extract 'INCREMENT' into its own variable so I can use it in multiple places. So I did this

const INCREMENT = 'INCREMENT'

type Increment = {
  type: INCREMENT
  payload: number
}

const action: Increment = {
  type: 'INCREMENT',
  payload: 1
}

console.log(action);

However the TS compiler is yelling at me and says

‘INCREMENT’ refers to a value, but is being used as a type here. Did
you mean ‘typeof INCREMENT’?

so I had to add typeof in front of INCREMENT to make it happy, as in

type Increment = {
  type: typeof INCREMENT
  payload: number
}

I don’t understand why is that when I am using a variable to reference the string suddenly the breaks the rule. Also shouldn’t typeof INCREMENT be a string string. I thought it would be equivalent to type: string but apparently here I cannot assign type with anything string other than INCREMENT. Can someone explain this to me?

3 Answers

In your first example, you were using a TypeScript feature called literal types which allow you to specify the exact value a string type variable can have. In this case the property type can only have the string value INCREMENT.

type Increment = {
  type: 'INCREMENT'
  payload: number
}

When you assigned that string literal to a variable using const, TypeScript no longer recognizes it as a type but as an actual value. Hence the error message 'INCREMENT' refers to a value....

If you assign that literal to a type variable that would work, as TypeScipt will see that as a type and not a value.

type INCREMENT = 'INCREMENT';
type Increment = {
  type: INCREMENT
  payload: number
};

However, you won't be able to "reuse" the actual string literal as a value it in multiple places.

One other solution I would recommend is using string enums. For example:

enum IncrementType {
  INCREMENT = 'INCREMENT',
  DECREMENT = 'DECREMENT' // Added this just as an example
}

type Increment = {
  type: IncrementTypes
  payload: number
}

const action: Increment = {
  type: IncrementType.INCREMENT,
  payload: 1
};

Answered by JoshA on September 11, 2020

It's because with const INCREMENT = 'INCREMENT' you have a string value but you're using it to declare a type. That's why typeof INCREMENT works; that's a string literal so you get type: "INCREMENT" which is like a sub-type of string (it is-a string). However, it can only have a single value of "INCREMENT". Like so:

const INCREMENT = 'INCREMENT'; // the apparent type is 'INCREMENT'
console.log(typeof INCREMENT); // the actual type is string

type Increment = {
  type: typeof INCREMENT,
  payload: number
}

const action: Increment = {
  type: INCREMENT,
  payload: 1
}
console.log(typeof action.type); // string
console.log(action.type); // INCREMENT

If you want use something like that in more than one place and will likely have more than one type. You can try an enum.

enum Type {
    INCREMENT = "INCREMENT",
    FOO = "FOO"
}

type Increment = {
  type: Type
  payload: number
}

const action: Increment = {
  type: Type.INCREMENT,
  payload: 1
}

console.log(action.type); // INCREMENT

Answered by ChiefTwoPencils on September 11, 2020

Try this: instead of const define new type

type INCREMENT = 'INCREMENT'

type Increment = {
  type: INCREMENT
  payload: number
}

const action: Increment = {
  type: 'INCREMENT',
  payload: 1
}

console.log(action);

Answered by Stanislav Berkov on September 11, 2020

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP