2

I'd like to have an interface generated out of the values of an enum. I have the following use-case in React:

I have an enum with potentially a lot of key value pairs. Each of the enum values is used as form IDs, so I get the name and the value of the input element in an event listener. I'd like to set the state to this.setState({ name: value }), but the name, description, etc. should be type-safe.

So I somehow need to generate an interface out of the values of the enum (because an interface cannot inherit from an enum) to be able to do the following for example: this.setState({ name: 'hello world' }) and this.setState({ description: 'a description' })

enum InputIDs {
    NAME = 'name',
    DESCRIPTION = 'description',
    // ... many more items ...
}

interface IMyReactComponentState {
    alreadyExisting: boolean;
    [InputIDs.NAME]: string;
    // ... all other items from the Test enum should go here but I'd like to generate it somehow ...
}

class MyReactComponent extends React.Component< ISomeProps, IMyReactComponentState > {
    constructor(props: ISomeProps) {
        super(props);
        this.state = {
            alreadyExisting: false,
            [InputIDs.NAME]: '',
            // more default values
        }
    }

    private handleChange = (event: React.FormEvent<HTMLDivElement>) => {
        // TODO make type safe
        const {name, value}: {name: any, value: string} = (event.target as any); // event.target is either HTMLInputElement, HTMLSelectElement or HTMLTextAreaElement

        // store the value of the corresponding input in state to preserve it when changing tabs
        this.setState({
            [name]: value
        });
  }

}

My problem is that something along these lines is not possible:

interface IMyReactComponentState extends InputIDs {
    alreadyExisting: boolean;
}

Any ideas how I can keep the enum with the typings of IMyReactComponentState in sync without writing an interface myself?

Thanks in advance! Not sure if this has been asked already - if so I haven't found the answer yet!


EDIT (May 8th, 2019):

We're using TypeScript 2.8.1 in our project

3 Answers 3

1

You need to use an interasection and a mapped type (the predefined Record mapped type should do)

enum InputIDs {
    NAME = 'name',
    DESCRIPTION = 'description',
    // ... many more items ...
}

type IMyReactComponentState = {
    alreadyExisting: boolean;
} & Record<InputIDs, string>

class MyReactComponent { // simplified
    state:IMyReactComponentState
    constructor() {        
        this.state = {
            alreadyExisting: false,
            [InputIDs.NAME]: '',
            [InputIDs.DESCRIPTION]: ''
        }
    }
}
Sign up to request clarification or add additional context in comments.

Comments

1

You can use mapped types to produce type with enum values as a keys, then use intersection or extend it with additional properties:

type InputValues = {
    [key in InputIDs]: string
}

Then

type IMyReactComponentState = InputValues & {
    alreadyExisting: boolean
};

Or:

interface IMyReactComponentState extends InputValues {
    alreadyExisting: boolean
}

Comments

0

You can define your IMyReactComponentState as

interface IMyReactComponentState {
    alreadyExisting: boolean;
    [key in keyof typeof InputIDs]: string 
}

1 Comment

thanks for your quick response. then I get the following error ']' expected.ts(1005) unused expression, expected an assignment or function calltslint(no-unused-expression) (I added a semicolon in the second line of the interface, although I think they're not needed in the last definition)

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.