Reputation: 7536
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.
Upvotes: 60
Views: 91713
Reputation: 1206
In TS 2.1 the keyof
keyword was introduced which made this possible:
function propertyOf<TObj>(name: keyof TObj) {
return name;
}
or
function propertiesOf<TObj>(_obj: (TObj | undefined) = undefined) {
return function result<T extends keyof TObj>(name: T) {
return name;
}
}
or using Proxy
export function proxiedPropertiesOf<TObj>(obj?: TObj) {
return 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<MyInterface>();
myInterfaceProperties.myProperty;
or
const myInterfaceProperties = proxiedPropertiesOf(myObj);
myInterfaceProperties.myProperty;
Upvotes: 84
Reputation: 2986
You can extract property name as string using keyof
and Pick
:
interface Test {
id: number,
title: string,
}
type TitleName = keyof Pick<Test, "title">;
//^? type TitleName = "title"
const okTitle: TitleName = "title";
const wrongTitle : TitleName = "wrong";
// Error: Type '"wrong"' is not assignable to type '"title"'
Upvotes: 2
Reputation: 11754
This is specifically for React/React-Native developers.
To safely get property-name, I use the below class:
export class BaseComponent<P = {}, S = {}> extends Component<P, S> {
protected getPropName = (name: keyof P) => name;
protected getStateName = (name: keyof S) => name;
}
And replaced extends React.Component<PropTypes>
with extends BaseComponnent<PropTypes
,
Now, with in the Component
you can call, this.getPropName('yourPropName')
to get the property name.
Upvotes: 3
Reputation: 106650
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";
Upvotes: 35