I'm looking for a way to get an object property name with typechecking that allows to catch possible regressions after refactoring.
Here's an example: the component where I have to pass the property names as strings and it will be broken if I'll try to change the property names in the model.
interface User { name: string; email: string; } class View extends React.Component<any, User> { constructor() { super(); this.state = { name: "name", email: "email" }; } private onChange = (e: React.FormEvent) => { let target = e.target as HTMLInputElement; this.state[target.id] = target.value; this.setState(this.state); } public render() { return ( <form> <input id={"name"} value={this.state.name} onChange={this.onChange}/> <input id={"email"} value={this.state.email} onChange={this.onChange}/> <input type="submit" value="Send" /> </form> ); } }
I'd appreciate if there's any nice solution to solve this issue.
To dynamically access an object's property: Use keyof typeof obj as the type of the dynamic key, e.g. type ObjectKey = keyof typeof obj; . Use bracket notation to access the object's property, e.g. obj[myVar] .
Noun. Definition: Identifies the property name that is displayed on the Property List; an easily recognizable or descriptive name; typically a street address or property name (e.g. “100 Main Street” or “Commerce Center Mall'). The default name is the Loan Name.
In TS 2.1 the keyof
keyword was introduced which made this possible:
const propertyOf = <TObj>(name: keyof TObj) => name;
or
const propertiesOf = <TObj>(_obj: (TObj | undefined) = undefined) => <T extends keyof TObj>(name: T): T => name;
or using Proxy
export const proxiedPropertiesOf = <TObj>(obj?: TObj) => new Proxy({}, { get: (_, prop) => prop, set: () => { throw Error('Set not supported'); }, }) as { [P in keyof TObj]?: P; };
These can then be used like this:
propertyOf<MyInterface>("myProperty");
or
const myInterfaceProperties = propertiesOf<MyInterface>(); myInterfaceProperties("myProperty");
or
const myInterfaceProperties = propertiesOf(myObj); myInterfaceProperties("myProperty");
or
const myInterfaceProperties = proxiedPropertiesOf(myObj); myInterfaceProperties.myProperty;
This will give an error if myProperty
is not a property of the type MyObj
.
Right now there's not really a great way of doing this, but there are currently some open suggestions on github (See #1579, #394, and #1003).
What you can do, is what's shown in this answer—wrap referencing the property in a function, convert the function to a string, then extract the property name out of the string.
Here's a function to do that:
function getPropertyName(propertyFunction: Function) { return /\.([^\.;]+);?\s*\}$/.exec(propertyFunction.toString())[1]; }
Then use it like so:
// nameProperty will hold "name" const nameProperty = getPropertyName(() => this.state.name);
This might not work depending on how the code is minified so just watch out for that.
Update
It's safer to do this at compile time. I wrote ts-nameof so this is possible:
nameof<User>(s => s.name);
Compiles to:
"name";
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With