Marc
Marc

Reputation: 317

Use type of variable as type of another typescript

Is it possible to use the value of a constant as a literal type for another? My use-case is Redux specific but it can be used elsewhere aswell.

I have actions defined as follow:

import { APP_NAME } from 'js/commons/constants/ReduxAppName';

const MODAL_OPEN = `${APP_NAME}/MODAL/OPEN`;
const MODAL_CLOSE = `${APP_NAME}/MODAL/CLOSE`;

export const types = {
    MODAL_OPEN,
    MODAL_CLOSE,
};

Now in my reducer I import those types:

import { types } from './modalTypes';
import { BaseActionI } from 'js/redux/interfaces/ReduxInterfaces';

const { MODAL_OPEN, MODAL_CLOSE } = types;

I want to make my reducer type safe. The payload of the action can be either a number (for closing) or a JSX.Element (when opening).

So I want to write something like this:

interface ModalCloseActionI extends BaseActionI {
    type: MODAL_OPEN,
    payload: number,
}

interface ModalOpenActionI extends BaseActionI {
    type: MODAL_CLOSE
    payload: JSX.Element
}

So then I can say my reducer expects an Action of type ModalOpenActionI | ModalCloseActionI

export const modalReducer = (state = Immutable.List<JSX.Element>(), action: ModalOpenActionI | ModalCloseActionI) => {
    const { type, payload } = action;
    switch (type) {
        case MODAL_OPEN: {
            return state.push(payload);
        }
        case MODAL_CLOSE: {
            return state.remove(payload as number);
        }
        default: {
            return state;
        }
    }
}

So then my switch cases are type-safed and the compiler can assume that inside MODAL_OPEN the payload actually is a JSX.Element

Is there a way to accomplish this without write much duplicate code?

Upvotes: 2

Views: 4008

Answers (1)

Nitzan Tomer
Nitzan Tomer

Reputation: 164167

You are trying to use a value as a type and that doesn't work.
The good news is that you can define the type using the same name:

const { MODAL_OPEN, MODAL_CLOSE } = types;
type MODAL_OPEN = string;
type MODAL_CLOSE = string;

That should make it work.


Edit

If you want the type to be of a single string literal, something like:

interface ModalCloseActionI extends BaseActionI {
    type: "close",
    payload: number,
}

interface ModalOpenActionI extends BaseActionI {
    type: "open"
    payload: JSX.Element
}

Then you won't be able to do that if the string values are not static, which is your case as you create them like so:

const MODAL_OPEN = `${APP_NAME}/MODAL/OPEN`;

2nd Edit

Even when using a static string literal you'll still need to declare it as a type:

const { MODAL_OPEN, MODAL_CLOSE } = types;
type MODAL_OPEN = "MODAL/OPEN";
type MODAL_CLOSE = "MODAL/CLOSE";

That's annoying because you need to redeclare each variable and you have repetition in the string value (which can lead to errors).

You can export a namespace instead of the const:

export namespace types {
    export const MODAL_OPEN = `MODAL/OPEN`;
    export type MODAL_OPEN = "MODAL/OPEN";

    export const MODAL_CLOSE = `MODAL/CLOSE`;
    export type MODAL_CLOSE = "MODAL/CLOSE";
}

Then the definitions are in the same place, but you will need to use it like this:

interface ModalCloseActionI {
    type: types.MODAL_OPEN,
    payload: number,
}

interface ModalOpenActionI {
    type: types.MODAL_CLOSE
    payload: JSX.Element
}

Upvotes: 3

Related Questions