Reputation: 317
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
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.
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`;
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