New Answer
Let's unpack the error message:
A computed property name in a type literal must refer to an expression whose type is a literal type is a literal type or a 'unique symbol' type.ts(1170)
"A computed property name" refers to the properties in your final ParamsList type - ( e.g. Route.Route1 in [Route.Route1]: undefined;).
So typescript is saying Route.Route1 must be an expression that ultimately evaluates to a "literal type" (e.g. a specific string like "Route1_N_1", or a specific number like 42), or a Symbol (not important here, but if you're curious, check the Symbol docs)
Here's what type Route.Route1 currently evaluates to:
`Route_${any}_1`
This known as a "template literal type" (see docs) - which is not the same thing as a literal string (e.g. "Route_N_1"). It sort of makes sense that TS would require this, too - it puts the compiler in a situation where it would be possible to have two (or more) different types defined for the same property on ParamsList, if there were multiple template literal types that could expand in overlapping ways.
So if you want to fix the problem, you need to somehow cause typescript to be able to evaluate Route.Route1 to a specific string, not a generic one.
The docs say that template literal types "have the ability to expand into many strings via unions," which is what you want. In order to trigger this, you must make sure that all the template positions (e.g. ${N} in Route_${N} get fed specific literal string types (e.g. "foo" or "bar", not things like any or string).
Here's one way to do this:
// const N: any = "N"; // original type "any" <--this won't work
const N = "N" as const; // new type "N" <-- this will
const PREFIX = `Route_${N}` as const; // new type: "Route_N"; original type: "Route_${any}"
const Route = {
Route1: `${PREFIX}_1`, // new type: "Route_N_1"; original type: "Route_${any}_1"
Route2: `${PREFIX}_2` // new type: "Route_N_2"; original type: "Route_${any}_2"
} as const;
type ParamsList = {
[Route.Route1]: undefined;
[Route.Route2]: { value: string };
};
See this playground example.
Update: How to provide a string literal type to something if the library you're building with doesn't provide it
In the above example, we're manually creating the N variable, so it's easy enough to provide a string literal type with as const assertion.
However, if N were actually something imported from another library, that had a type of any, we would need to somehow provide this type ourselves. As you pointed out in the comments, one way to do this would be to add an as "MyString" assertion to the end.
Another way would be to use module augmentation combined with declaration merging. In the specific example you provided in the comments, you were doing this:
import { StyleSheet, Text, View, NativeModules } from "react-native";
const { NativeNaviagtion } = NativeModules;
// NativeNavigation.Page1 <-- this has type "any"
You can "augment" the NativeModules type provided by the "react-native" module so that NativeModules.NativeNavigation.Page1 has a literal string type "Page1" by writing:
declare module "react-native" {
interface NativeModulesStatic {
NativeNaviagtion: { Page1: "Page1" };
}
}
See this codesandbox example and this answer for a similar problem in a different context.
Old Answer
Your original example (without the additional layer of indirection with const N: any = "N") will work in TypeScript 4.1+, but not in TypeScript 4.0-. That's because TS added the Template Literal Type feature in 4.1.
Before 4.1, the Route const would have this type:
const Route: {
readonly Route1: string;
readonly Route2: string;
}
After 4.1, the Route const has this type:
const Route: {
readonly Route1: "Route_1";
readonly Route2: "Route_2";
}
So with 4.0- versions of typescript, this...
type ParamsList = {
[Route.Route1]: undefined;
[Route.Route2]: { value: string };
};
...would be interpreted as this...
type ParamsList = {
[string]: undefined;
[string]: { value: string };
};
...which is ambiguous, and throws that error. In 4.1+, that gets correctly interpreted as:
type ParamsList = {
Route_1: undefined;
Route_2: {
value: string;
};
}