Replace
[field: string]: {
property1: string,
property2: string
}
by
interface Anything {
[key: string]: any;
}
And when you're calling then: do this
let item = { property1: "randomTest", property2: "randomTest1", property3: "randomTest3"}
this.setState({
[item].property1: "test value 1"
})
Answer from mdev on Stack OverflowReplace
[field: string]: {
property1: string,
property2: string
}
by
interface Anything {
[key: string]: any;
}
And when you're calling then: do this
let item = { property1: "randomTest", property2: "randomTest1", property3: "randomTest3"}
this.setState({
[item].property1: "test value 1"
})
Add a semecon before testKey1: string;
reactjs - Dynamic object key with Typescript in React event handler - Stack Overflow
reactjs - Pick<S, K> type with dynamic/computed keys - Stack Overflow
reactjs - React + TypeScript set state with dynamic key names - Stack Overflow
Dynamic object keys in TypeScript
So after doing more research I can provide a little more context on what is happening in the above code.
When you do something like const name = 'Bob' the type of the variable name is 'Bob' not string. However, if you replace the const with a let (let name = 'Bob') the variable name will be of type string.
This concept is called "widening". Basically, it means that the type system tries to be as explicit as possible. Because const can not be reassigned TypeScript can infer the exact type. let statements can be reassigned. Thus, TypeScript will infer string (in the above example) as the type of name.
The same is happening with const key = e.currentTarget.name as keyof Person. key will be of (union) type "name"|"age", which is exactly what we want it to be. But in the expression this.setState({ [key]: value }); variable key is (incorrectly) widened to a string.
tl;dr; It seems like there is a bug in TypeScript. I posted the issue to the Github repo and the TypeScript team is investigating the problem. :)
As a temporary workaround you can do:
this.setState({ [key as any]: value });
The answer from Sebastian no longer works for me (though at one stage it did). I now do the following until it is resolved in Typescript core (as per the issue listed in Sebastians answer):
handleUpdate (e:React.SyntheticEvent<HTMLInputElement>) {
const newState = {};
newState[e.currentTarget.name] = e.currentTarget.value;
this.setState(newState);
}
It's lame, but it works
Index signatures
It is possible to denote obj as any, but that defeats the whole purpose of using typescript. obj = {} implies obj is an Object. Marking it as any makes no sense. To accomplish the desired consistency an interface could be defined as follows, using an index signature
interface LooseObject {
[key: string]: any
}
var obj: LooseObject = {};
OR to make it compact:
var obj: {[k: string]: any} = {};
LooseObject can accept fields with any string as key and any type as value.
obj.prop = "value";
obj.prop2 = 88;
The real elegance of this solution is that you can include typesafe fields in the interface.
interface MyType {
typesafeProp1?: number,
requiredProp1: string,
[key: string]: any
}
var obj: MyType ;
obj = { requiredProp1: "foo"}; // valid
obj = {} // error. 'requiredProp1' is missing
obj.typesafeProp1 = "bar" // error. typesafeProp1 should be a number
obj.prop = "value";
obj.prop2 = 88;
Record<Keys,Type> utility type
Update (August 2020): @transang brought up the Record<Keys,Type> utility type in comments
Record<Keys,Type>is a Utility type in typescript. It is a much cleaner alternative for key-value pairs where property-names are not known. It's worth noting thatRecord<Keys,Type>is a named alias to{[k: Keys]: Type}whereKeysandTypeare generics. IMO, this makes it worth mentioning here
For comparison,
var obj: {[k: string]: any} = {};
becomes
var obj: Record<string,any> = {}
MyType can now be defined by extending Record type
interface MyType extends Record<string,any> {
typesafeProp1?: number,
requiredProp1: string,
}
While this answers the Original question, the answer here by @GreeneCreations might give another perspective on how to approach the problem.
This solution is useful when your object has Specific Type. Like when obtaining the object to other source.
let user: User = new User();
(user as any).otherProperty = 'hello';
//user did not lose its type here.
Hi guys,
I'm trying to set a React component's state dynamically basically off an input element. The type-checker is not working as I'd thought. I'm getting error: Missing property from type Pick<State, "person" | "anotherStateVar">
Could someone advise how I should be doing this? I don't want to cast as any, or State - as this defeats the whole purpose of type-checking. Is there something obvious I'm missing?
type State = {
person: string,
anotherStateVar: string,
}
handleChange = async (event: React.FormEvent<HTMLInputElement>) => {
const {name, value} = event.currentTarget
try {
await externalServiceCall()
//setting state dynamically + have ability to set another key if I choose
this.setState({[name]: {value}})
//this.setState({[name]: {value}, anotherStateVar: 'hello'})
} catch (e) {
//error things
}
}
edit: formatting
The problem is that e.target.name should be key of IState and e.target.value should be relative type from key in IState, to fix it you can manually cast type like
this.setState({
[e.target.name]: e.target.value
} as Pick<IState, keyof IState>);
but it disables typescript for this function also you can cast it to any type, but it's unsafe.
Looks like this answer also can be helpful for you
The best option is to use ReduxForm
We can also do something like this:
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
const name = e.target.name as keyof typeof this.state;
this.setState({ ...this.state, [name]: e.target.value });
};
I found the answer here: https://stackoverflow.com/a/55014391
Typing the COMPONENTS object fixed my problem.
import React, {useState, useEffect} from 'react'
import ComponentA from '@components/ComponentA';
import ComponentB from '@components/ComponentB';
const DynamicComponent: React.FC = ({key}) => {
const [Component, setComponent] = useState<any>();
const COMPONENTS: {[key: string]: React.FC<any>} = {
COMPONENT_A: ComponentA,
COMPONENT_B: ComponentB,
};
useEffect(() => {
if (key) setComponent(COMPONENTS[key])
}, [key]);
return Component ? React.createElement(Component) : null;
};
You need define PropTypes for your component, also type of Key.
import React, {useState, useEffect} from 'react'
import ComponentA from '@components/ComponentA';
import ComponentB from '@components/ComponentB';
type Key = "COMPONENT_A" | "COMPONENT_B"
type Props = { key: Key }
const DynamicComponent: React.FC<Props> = ({key}) => {
const [Component, setComponent] = useState<any>();
const COMPONENTS = {
COMPONENT_A: ComponentA,
COMPONENT_B: ComponentB,
};
useEffect(() => {
if (key) setComponent(COMPONENTS[key])
}, [key]);
return Component ? React.createElement(Component) : null;
};
I've been able to find a way out using the code snippet below
export const validateObjectId = (key: string = 'id'): ObjectSchema => {
interface Obj {
[key: string]: Object;
}
const object: Obj = {};
object[key] = Joi.string()
.regex(/^(?=[a-f\d]{24}$)(\d+[a-f]|[a-f]+\d)/i)
.required();
return Joi.object(object);
};
Maybe you can try the following:
export const validateObjectId = (key: string = 'id'): ObjectSchema => {
let object: any = {};
object[key] = Joi.string()
.regex(/^(?=[a-f\d]{24}$)(\d+[a-f]|[a-f]+\d)/i)
.required();
return Joi.object(object);
}
Cheers
I think this code will illustrate what I'm trying to accomplish:
function someFunction(dynamicName: string) {
return {
[dynamicName]: 'foo',
hardCodedName: 'bar'
}
}
const someObject = someFunction('myExample')
console.log(someObject.myExample) // Works
console.log(someObject.hardCodedName) // Works
console.log(someObject.whatever) // undefined! But TypeScript is OK with itAny suggestions?
Hello friends,
I am working on an application that will require dynamic typing of an object. The user should be able to define a certain list of attributes they want to track. The list of attributes they create for their tracking will be stored in the back-end Mongo DB. Basically each user should have an "attributes" object that extends the default object with whatever additional key:value pairs they want to track.
I'm curious how this is possible when using Typescript, since it seems to want everything to be pre-defined.
Here is an example of the current statically-typed object:
export interface IDay {id: number;dayRating?: number | undefined;sleep?: number | undefined;date?: string | undefined;notes?: string | undefined;}