ยท Jake Worth

Union type from array


You often want the same string list in three places: runtime values, a state type, and function parameters. Duplicating a union by hand drifts out of sync:

component.tsx
const builderSteps = ['communications', 'estimates', 'procedures'];
type BuilderStep = 'communications' | 'estimates' | 'procedures';

With as const and an indexed access type, the array is the single source of truth:

component.tsx
const builderSteps = ['communications', 'estimates', 'procedures'] as const;
type BuilderStep = (typeof builderSteps)[number];
// 'communications' | 'estimates' | 'procedures'

This lets us both type a slice of state, type a function that might receive that state, and build TSX elements all from the same array, a virtuous cycle.

component.tsx
import {useState} from 'react';
const [step, setStep] = useState<BuilderStep>('communications');
const handleStepClick = (step: BuilderStep) => setStep(step);
{
builderSteps.map((step) => (
<button key={step} onClick={() => handleStepClick(step)}>
Set step: {step}
</button>
));
}

When we skip as const, TypeScript widens the array to string[]. Then (typeof builderSteps)[number] becomes string, and you lose the union. The const assertion freezes the elements as readonly literal types so the [number] lookup can turn the array into a union.

TypeScript documentation - const assertions