To do the assignment, you have to reassure TypeScript that the key you're trying to use identifies a string property in Generation.
There are a couple of ways to do that.
Generic Extracting String/Number Properties and Type Predicates
You can use a generic like this to get the keys for properties that are assignable to a given type:
type AssignableKeys<Source extends object, Target> = Exclude<{
[Key in keyof Source]: Required<Source>[Key] extends Target ? Key : never;
}[keyof Source], undefined>
(More about how that works in my answer here, which is derived [no pun!] from this answer by Titian Cernicova-Dragomir.)
Then a type predicate to narrow the type of the string key you're using (this version repeats property names, but keep reading); notice the AssignableKeys<Generation, string> type at the end, saying the key identifies a string-typed property of Generation:
function isGenerationStringKey(key: string): key is AssignableKeys<Generation, string> {
switch (key) {
case "name":
case "latitude":
case "longitude":
case "source":
return true;
default:
return false;
}
}
...and/or a type assertion function that asserts the key is valid:
function assertIsGenerationStringKey(key: string): asserts key is AssignableKeys<Generation, string> {
if (!isGenerationStringKey(key)) {
throw new Error(`Key "${key}" doesn't specify a string-typed Generation property`);
}
}
Then your function can use the type predicate to narrow the type of the key:
const handleGenerationFormChange = (event:React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
let genHolder = currentGeneration;
const key = event.target.name;
if (isGenerationStringKey(key)) {
genHolder[key] = event.target.value;
}
};
or with the type assertion function:
const handleGenerationFormChange = (event:React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
let genHolder = currentGeneration;
const key = event.target.name;
assertIsGenerationStringKey(key);
genHolder[key] = event.target.value;
};
Repeat it for the numeric properties; perhaps you'd have a separate event handler for those that converts value to number and uses a number-oriented type predicate.
Playground link
Example Objects and Derived Types (and Type Predicates)
Often when I want names at compile-time and also at runtime (for instance, for type predicates), I do it by having example objects and deriving the type from them. In your case:
// The example objects
// NOTE: You can put documentation comments on these properties,
// and the comments will be picked up by the derived types
const generationStrings = {
name: "",
latitude: "",
longitude: "",
source: "",
};
const generationNumbers = {
id: 0,
max_power: 0,
current_power: 0,
};
// Deriving types:
type GenerationStringProperties = typeof generationStrings;
type GenerationNumberProperties = typeof generationNumbers;
type Generation = Partial<GenerationStringProperties & GenerationNumberProperties>;
The type predicate and/or type assertion function is simpler and doesn't repeat names
function isGenerationStringKey(key: string): key is keyof GenerationStringProperties {
return key in generationStrings;
}
function assertIsGenerationStringKey(key: string): asserts key is keyof GenerationStringProperties {
if (!isGenerationStringKey(key)) {
throw new Error(`Key "${key}" doesn't specify a string-typed Generation property`);
}
}
The usage in the event handler is the same as above.
Playground link
GenerationorGenerationInterface? You seem to be using them interchangeably.