I am very much a novice in TypeScript and I spent at least half my time just randomly changing variables to if the IntelliSense error clears.
I am working on a system that pulls in Ceramic streams and caches them in a Hasura instance. The fields in Ceramic sometimes have slightly different names than what is used in Hasura. The system imports profile information from two streams: Ceramic’s basicProfile & a homegrown extendedProfile.
I am trying to use union types to capture the field name mappings, but things aren't quite working. My types are unions where the key is the Hasura id and the value is the Ceramic id:
export const BasicProfileImages = {
imageURL: 'image',
bannerImageURL: 'background',
} as const;
export const BasicProfileStrings = {
name: 'name',
description: 'description',
emoji: 'emoji',
location: 'homeLocation',
countryCode: 'residenceCountry',
birthDate: 'birthDate',
website: 'url',
} as const;
export const BasicProfileFields = {
...BasicProfileImages,
...BasicProfileStrings,
} as const;
export const ExtendedProfileImages = {
backgroundImageURL: 'background',
} as const;
export const ExtendedProfileStrings = {
username: 'username',
pronouns: 'pronouns',
magicDisposition: 'magicDisposition',
} as const;
export type BPImages = {
-readonly [key in keyof typeof BasicProfileImages as string]?: ImageSources;
};
export type BPStrings = {
-readonly [key in keyof typeof BasicProfileStrings as string]?: string;
};
export type EPImages = {
-readonly [key in keyof typeof ExtendedProfileImages as string]?: ImageSources;
};
export type EPStrings = {
-readonly [key in keyof typeof ExtendedProfileStrings as string]?: string;
};
export interface TimeZone {
name?: string;
abbreviation: string;
location?: string;
offset: number;
}
export interface TitledDescription {
title: string;
description?: string;
}
export type EPObjects = {
availableHours?: number;
timeZone?: TimeZone;
playerType?: TitledDescription;
}
export type ProfileProps = (
BPStrings | BPImages | EPImages | EPStrings | EPObjects
);
export const Images = {
...BasicProfileImages,
...ExtendedProfileImages,
} as const;
export const Strings = {
...BasicProfileStrings,
...ExtendedProfileStrings,
} as const;
export type Endpoints = {
-readonly [key in keyof typeof Images]?: {
val: string;
set: () => void;
ref: MutableRefObject<HTMLImageElement>;
};
};
Then in my code I iterate over the Images and generate a URL value, setter and a reference for each image:
const endpoints = Object.fromEntries(
Object.keys(Images).map((hasuraId) => {
const key = hasuraId as keyof typeof Images;
// eslint-disable-next-line react-hooks/rules-of-hooks
const [url, set] = useState<Maybe<string>>(
player?.profile?.[key] ?? null,
);
// eslint-disable-next-line react-hooks/rules-of-hooks
const ref = useRef<HTMLImageElement>(null);
return [key, { val: url, set, ref }]; // key ends in “URL”
}),
);
Then I iterate over that structure in my onSubmit handler to access the selected images:
const onSubmit = async (inputs: ProfileProps) => {
⋮
const files: Record<string, File> = {};
Object.keys(Images).forEach((hasuraId) => {
const key = hasuraId as keyof typeof Images;
const fileList = (inputs[key] ?? []) as File[];
if (fileList.length > 0) {
[files[key]] = fileList;
}
delete inputs[key]; // eslint-disable-line no-param-reassign
});
The error I'm getting is for inputs[key]:
const key: "backgroundImageURL" | "imageURL" | "bannerImageURL"
Element implicitly has an 'any' type because expression of type '"imageURL" | "bannerImageURL" | "backgroundImageURL"' can't be used to index type 'ProfileProps'.
Property 'imageURL' does not exist on type 'ProfileProps'.