Reputation: 9606
Trying out TypeScript for a React project and I'm stuck on this error:
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ train_1: boolean; train_2: boolean; train_3: boolean; train_4: boolean; }'.
No index signature with a parameter of type 'string' was found on type '{ train_1: boolean; train_2: boolean; train_3: boolean; train_4: boolean; }'
Which appears when I try to filter the array in my component
.filter(({ name }) => plotOptions[name]);
So far I looked at the article "Indexing objects in TypeScript" (https://dev.to/kingdaro/indexing-objects-in-typescript-1cgi) since it had a similar error, but I tried to add the index signature to type plotTypes
and I still get the same error.
My component code:
import React, { Component } from "react";
import createPlotlyComponent from "react-plotly.js/factory";
import Plotly from "plotly.js-basic-dist";
const Plot = createPlotlyComponent(Plotly);
interface IProps {
data: any;
}
interface IState {
[key: string]: plotTypes;
plotOptions: plotTypes;
}
type plotTypes = {
[key: string]: boolean;
train_1: boolean;
train_2: boolean;
train_3: boolean;
train_4: boolean;
};
interface trainInfo {
name: string;
x: Array<number>;
y: Array<number>;
type: string;
mode: string;
}
class FiltrationPlots extends Component<IProps, IState> {
readonly state = {
plotOptions: {
train_1: true,
train_2: true,
train_3: true,
train_4: true
}
};
render() {
const { data } = this.props;
const { plotOptions } = this.state;
if (data.filtrationData) {
const plotData: Array<trainInfo> = [
{
name: "train_1",
x: data.filtrationData.map((i: any) => i["1-CumVol"]),
y: data.filtrationData.map((i: any) => i["1-PressureA"]),
type: "scatter",
mode: "lines"
},
{
name: "train_2",
x: data.filtrationData.map((i: any) => i["2-CumVol"]),
y: data.filtrationData.map((i: any) => i["2-PressureA"]),
type: "scatter",
mode: "lines"
},
{
name: "train_3",
x: data.filtrationData.map((i: any) => i["3-CumVol"]),
y: data.filtrationData.map((i: any) => i["3-PressureA"]),
type: "scatter",
mode: "lines"
},
{
name: "train_4",
x: data.filtrationData.map((i: any) => i["4-CumVol"]),
y: data.filtrationData.map((i: any) => i["4-PressureA"]),
type: "scatter",
mode: "lines"
}
].filter(({ name }) => plotOptions[name]);
return (
<Plot
data={plotData}
layout={{ width: 1000, height: 1000, title: "A Fancy Plot" }}
/>
);
} else {
return <h1>No Data Loaded</h1>;
}
}
}
export default FiltrationPlots;
Upvotes: 864
Views: 1104036
Reputation: 38740
I want to add another edge case in which this error might occur. I have had this happen due to a broken dependency in node_modules
that did not properly export its types. So, updating from one version to another, suddently this type of error would appear, because tsc
could not fully resolve everything.
This led to a lot of
Element implicitly has an 'any' type
errors.
Ensure that the origin of this error is not with a dependent package whose package does not export correct types.
Upvotes: 0
Reputation: 3685
For use with a reduce
function. Make sure you properly type the accumulator.
Example:
const getNestedLevels = (
subjects: TutorSubject[] | TutorActivity[] | undefined,
) => {
if (subjects === undefined) {
return {};
}
return subjects.reduce(
(acc, curr) => {
let subject;
let level;
if (curr.specialization) {
subject = curr.specialization.name;
}
if (curr.specializationLevel) {
level = curr.specializationLevel.name;
}
if (subject && level) {
acc[subject] = [...(subject in acc ? acc[subject] : []), level];
}
return acc;
},
{} as { [key: string]: string[] }, // 👈 type this acc
);
};
Upvotes: 5
Reputation: 791
I ran into this issue today as well, tried what mentioned above to use keyof
, but it thorws another error saying "Type 'any' is not assignable to type 'never".
Did some research and turns out it's because ts thinks that the key I have could be any string, and such key string may not exist in my object
take this for example
interface MyObj {
[abc as string]: string;
[xyz as string]: boolean;
}
const flag: boolean = true;
const newObj: MyObj = cloneDeep(myObj);
const myKey: string = someKeyFromSomewhereXYZ; // for my situation my key was passed in as a child of an object prop from parent component
newObj[myKey as keyof MyObj] = !flag; // this is where my error pops, and doing keyof won't fix the issue.
so for me to fix the issue, what I did was to add the specific key to myKey
, make it look like this
const myKey: 'xyz' = someKeyFromSomewhereXYZ
it seems like ts thinks that myKey
could be any sting, hence doing newObj[myKey]
could be undefined
, which leads to 'never' in ts I guess.
Upvotes: 0
Reputation: 1422
You can also utilize switch
with fall-through statements:
switch (prop) {
case 'propOne':
case 'propTwo':
case 'propThree':
return someObj[prop];
default:
return false;
}
As long as those props exist on someObj
, you will not get a TypeScript error.
Upvotes: 0
Reputation: 17432
I fixed this issue by using keyof
messageMap = {
"Hi": "Hello",
"Who are you": "My name is Test Sat Bot",
"What is your role": "Just guide for the user",
}
this ❌
let answer = this.messageMap[question];
replace with ✔️
let answer = this.messageMap[question as keyof typeof this.messageMap];
Here question
is type of string
getBotMessage(question: string){
let answer = this.messageMap[question as keyof typeof this.messageMap];
}
Upvotes: 106
Reputation: 674
This might help someone
First the empty object need to be with the type Record<string, any>
so it will accept key as string
and value as any
const sortFields: Record<string, any> = {};
const keyName = `field`;
sortFields[keyName] = 'asc';
So here when we log sortFields
console.log("sortFields==== ", sortFields); // sortFields==== {field: 'asc'}
Or if we want to add more than one element in the object we can do it like this
sortFields.email = "asc"; // Or sortFields["email"] = "asc"; but its better written in dot notation.
sortFields.name = "desc";
sortFields.phone = "desc";
So now when we log sortFields
console.log("sortFields==== ", sortFields); // sortFields==== {field: 'asc', email: 'asc', name: 'desc', phone: 'desc'}
Upvotes: 14
Reputation: 2033
In situation of nested object, if want for in
in for in
, here is a working example without error warning:
export const X = {
aa: {
a: '1',
b: '2',
},
bb: {
a: '3',
b: '4',
},
} as const
export const f1 = () => {
let k1: keyof typeof X
for (k1 in X) {
console.log(k1)
let k2: keyof (typeof X)[keyof typeof X]
for (k2 in X[k1]) {
console.log(X[k1][k2])
}
}
}
Upvotes: 1
Reputation: 49530
as a last resort, you can mute this error by setting "suppressImplicitAnyIndexErrors": true
in tsconfig.json
{
"compilerOptions": {
"suppressImplicitAnyIndexErrors": true,
}
}
Upvotes: 8
Reputation: 8566
TypeScript needs to be sure those values exist in trainInfo, else it reads all as string
instead of doing this
interface trainInfo {
name: string;
x: Array<number>;
y: Array<number>;
type: string;
mode: string;
}
do this
interface trainInfo {
name: "train_1" | "train_2" | "train_3"| "train_4";
x: Array<number>;
y: Array<number>;
type: string;
mode: string;
}
Upvotes: 1
Reputation: 15696
For anyone who stumbles upon this in the future:
If you're getting the TypeScript error
'...expression of type string cannot be used to index...'
then simply specify that the 'expression of type string' is a key of the type of that object. For example,
const someObj:ObjectType = data;
const field = 'username';
// This gives an error
const temp = someObj[field];
// Solution 1: When the type of the object is known
const temp = someObj[field as keyof ObjectType]
// Solution 2: When the type of the object is not known
const temp = someObj[field as keyof typeof someObj]
Upvotes: 1217
Reputation: 38199
It worked for me with keyof
and as
operators:
const keys: [keyof ITrainInfo] = Object.keys(this.trainInfo) as [
keyof ITrainInfo,
]
keys.forEach((property) => {
// console.log(tmpUser[property])
if (this.trainInfo === undefined) return
if (this.trainInfo[property] !== undefined) {
// your code here
/*const trainsToSet = trains.find((field) => field.name === property)
if (trainsToSet != undefined)
trainsToSet.value = this.trainInfo[property]?.toString()
*/
}
})
Upvotes: 6
Reputation: 79
public getUserName(): string {
const accessToken = this.getAccessToken();
const claims:any = this.getUserClaims();
console.log('access token ',accessToken);
this.getUserInfo();
return claims['sub'].split('@')[0];
}
//give any type to the variable
Upvotes: -1
Reputation: 9630
With out typescript
error
const formData = new FormData();
Object.keys(newCategory).forEach((k,i)=>{
var d =Object.values(newCategory)[i];
formData.append(k,d)
})
Upvotes: 8
Reputation: 1779
This is not a answer to the original question, but a generic work around to this problem.
Original problem:
person[cr.field]
causes this error
I'm doing a generic advanced search form where user can select a field, comparator and the desired value. When trying to read the value from the object based on the key, I get this error (altought the field
value is type of string and I think it should be just fine)
So what I do is I extract the [key, value] like this
const x: [string, any] = Object.entries(person).find(([key, _]) => key === cr.field);
For example if my criterion (cr
) is { field: 'name', value: 'John' }
and field name
actually exists in a person obj., it should return the field name and the value as tuple (x is [string, any]
or undef). If not found, undefined.
Upvotes: 0
Reputation: 1
I know it's a little too late, but all it's needed is to add a little type conversion, I wrote a static function that safely-returns the array of keys with the correct typing. All you need is to define the type and pass the object as a parameter:
export class ObjectUtil {
public static getObjectKeys<T>(obj: Object) {
if (!obj) {
return [];
}
return Object.keys(obj).map((key: string) => key as keyof T);
}
}
Below is a simple example:
ObjectUtil.getObjectKeys<Address>(address).forEach((key) => {
console.log(address[key]);
});
Upvotes: -1
Reputation: 1285
I use this:
interface IObjectKeys {
[key: string]: string | number;
}
interface IDevice extends IObjectKeys {
id: number;
room_id: number;
name: string;
type: string;
description: string;
}
NOTE: "[key: string]" what is it? An object in JavaScript is primarily just a collection of properties made up of key-value pairs. Moreover, the key can only be a string (even for array elements), but the value can be any data type.
If you use the optional property in your object:
interface IDevice extends IObjectKeys {
id: number;
room_id?: number;
name?: string;
type?: string;
description?: string;
}
... you should add 'undefined' value into the IObjectKeys interface:
interface IObjectKeys {
[key: string]: string | number | undefined;
}
Upvotes: 104
Reputation: 461
I have made a simulation of the problem. looks like the issue is how we should Access Object Properties Dynamically Using Bracket Notation in Typescript
interface IUserProps {
name: string;
age: number;
}
export default class User {
constructor(private data: IUserProps) {}
get(propName: string): string | number {
return this.data[propName as keyof IUserProps];
}
}
I found a blog that might be helpful to understand this better.
here is a link https://www.nadershamma.dev/blog/2019/how-to-access-object-properties-dynamically-using-bracket-notation-in-typescript/
Upvotes: 32
Reputation: 53
I made some small changes to Alex McKay's function/usage that I think make it a little easier to follow why it works and also adheres to the no-use-before-define rule.
First, define this function to use:
const getKeyValue = function<T extends object, U extends keyof T> (obj: T, key: U) { return obj[key] }
In the way I've written it, the generic for the function lists the object first, then the property on the object second (these can occur in any order, but if you specify U extends key of T
before T extends object
you break the no-use-before-define
rule, and also it just makes sense to have the object first and its' property second. Finally, I've used the more common function syntax instead of the arrow operators (=>
).
Anyways, with those modifications you can just use it like this:
interface User {
name: string;
age: number;
}
const user: User = {
name: "John Smith",
age: 20
};
getKeyValue(user, "name")
Which, again, I find to be a bit more readable.
Upvotes: 4
Reputation: 19798
When using Object.keys
, the following works:
Object.keys(this)
.forEach(key => {
console.log(this[key as keyof MyClass]);
});
Upvotes: 147
Reputation: 509
Thanks to Alex Mckay I had a resolve for dynamic setting a props:
for(let prop in filter)
(state.filter as Record<string, any>)[prop] = filter[prop];
Upvotes: 7
Reputation: 3706
// bad
const _getKeyValue = (key: string) => (obj: object) => obj[key];
// better
const _getKeyValue_ = (key: string) => (obj: Record<string, any>) => obj[key];
// best
const getKeyValue = <T extends object, U extends keyof T>(key: U) => (obj: T) =>
obj[key];
Bad - the reason for the error is the object
type is just an empty object by default. Therefore it isn't possible to use a string
type to index {}
.
Better - the reason the error disappears is because now we are telling the compiler the obj
argument will be a collection of string/value (string/any
) pairs. However, we are using the any
type, so we can do better.
Best - T
extends empty object. U
extends the keys of T
. Therefore U
will always exist on T
, therefore it can be used as a look up value.
Here is a full example:
I have switched the order of the generics (U extends keyof T
now comes before T extends object
) to highlight that order of generics is not important and you should select an order that makes the most sense for your function.
const getKeyValue = <U extends keyof T, T extends object>(key: U) => (obj: T) =>
obj[key];
interface User {
name: string;
age: number;
}
const user: User = {
name: "John Smith",
age: 20
};
const getUserName = getKeyValue<keyof User, User>("name")(user);
// => 'John Smith'
const getKeyValue = <T, K extends keyof T>(obj: T, key: K): T[K] => obj[key];
Upvotes: 84
Reputation: 2256
When we do something like this obj[key] Typescript can't know for sure if that key exists in that object. What I did:
Object.entries(data).forEach(item => {
formData.append(item[0], item[1]);
});
Upvotes: 15
Reputation: 148
This is what it worked for me. The tsconfig.json
has an option noImplicitAny
that it was set to true
, I just simply set it to false
and now I can access properties in objects using strings.
Upvotes: -29
Reputation: 11848
This happens because you try to access plotOptions
property using string name
. TypeScript understands that name
may have any value, not only property name from plotOptions
. So TypeScript requires to add index signature to plotOptions
, so it knows that you can use any property name in plotOptions
. But I suggest to change type of name
, so it can only be one of plotOptions
properties.
interface trainInfo {
name: keyof typeof plotOptions;
x: Array<number>;
y: Array<number>;
type: string;
mode: string;
}
Now you'll be able to use only property names that exist in plotOptions
.
You also have to slightly change your code.
First assign array to some temp variable, so TS knows array type:
const plotDataTemp: Array<trainInfo> = [
{
name: "train_1",
x: data.filtrationData.map((i: any) => i["1-CumVol"]),
y: data.filtrationData.map((i: any) => i["1-PressureA"]),
type: "scatter",
mode: "lines"
},
// ...
}
Then filter:
const plotData = plotDataTemp.filter(({ name }) => plotOptions[name]);
If you're getting data from API and have no way to type check props at compile time the only way is to add index signature to your plotOptions
:
type tplotOptions = {
[key: string]: boolean
}
const plotOptions: tplotOptions = {
train_1: true,
train_2: true,
train_3: true,
train_4: true
}
Upvotes: 411