Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type #35859

Closed
ajhadi opened this issue Dec 26, 2019 · 16 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@ajhadi
Copy link

ajhadi commented Dec 26, 2019

TypeScript Version: 3.0.1

Code

import Sequelize from 'sequelize';
import { DbInterface } from'typings/DbInterface';
import { UserFactory } from './User';
import { PostFactory } from './Post';
import { CommentFactory } from './Comment';

export const createModels = (sequelizeConfig: any): DbInterface => {
  const { database, username, password, params } = sequelizeConfig;
  const sequelize = new Sequelize(database, username, password, params);

  const db: DbInterface = {
    sequelize,
    Sequelize,
    Comment: CommentFactory(sequelize, Sequelize),
    Post: PostFactory(sequelize, Sequelize),
    User: UserFactory(sequelize, Sequelize)
  };

  Object.keys(db).forEach(modelName => {
    if (db[modelName].associate) { //Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'DbInterface'. No index signature with a parameter of type 'string' was found on type 'DbInterface'.
      db[modelName].associate(db); //Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'DbInterface'. No index signature with a parameter of type 'string' was found on type 'DbInterface'.
    }
  });

  return db;
};
@dragomirtitian
Copy link
Contributor

This is a question, not a bug, please use SO for questions. (see this it will help)

@ajhadi
Copy link
Author

ajhadi commented Dec 26, 2019

im sorry, thanks btw for the tips. im a newbie

@RyanCavanaugh RyanCavanaugh added the Question An issue which isn't directly actionable in code label Jan 13, 2020
@alexandermckay
Copy link

// 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.

@pke
Copy link

pke commented May 28, 2020

@alexandermckay What if I have this scenario:

type props = {
  type: string
}

const style = styles[props.type]

const styles = StyleSheet.create({
  base: {
    ...spacing.medium,
    padding: 10,
    textAlign: "center",
    textAlignVertical: "center",
    fontWeight: "bold",
    fontSize: 18,
    height: 50,
  },
  primary: {
    backgroundColor: colors.purple,
    color: colors.white,
  },
  secondary: {
    backgroundColor: "transparent",
    color: colors.purple,
    borderColor: colors.purple,
    borderWidth: 2,
  },
}

It complains:
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type styles

@greenlaw110
Copy link

Same question here, I have

const LEVEL_DEBUG = 1;
const LEVEL_FINE = 2;

const LOG_LEVELS = {
  info: LEVEL_INFO,
  debug: LEVEL_DEBUG,
  fine: LEVEL_FINE,
};

let logLevel: string = environment.logLevel;
if (!logLevel) {
  logLevel = 'info';
}
let _level = LOG_LEVELS[logLevel.toLowerCase()]; // here it complains 

@chihshenghuang
Copy link

@greenlaw110
Try to get the key value by using @alexandermckay's method:

const LEVEL_DEBUG = 1;
const LEVEL_FINE = 2;


const LOG_LEVELS: {[key:string]: number} = {
  debug: LEVEL_DEBUG,
  fine: LEVEL_FINE,
};

let logLevel:string = 'debug';
if (!logLevel) {
  logLevel = 'info';
}

const getKeyValue = <T extends object, U extends keyof T>(obj: T) => (key: U) =>
  obj[key];
let _level = getKeyValue(LOG_LEVELS)(logLevel);
console.log(_level); // 1

@bradydowling
Copy link

bradydowling commented Aug 25, 2020

Just found this blog post, which was very helpful, and wanted to paste some code here that I think is slightly more straight forward than the code included here so far. If you have the following object:

const unitsOfTime = {
  millisecond: 1,
  second: 60,
  hour: 60 * 60,
  day: 24 * 60 * 60,
  month: 30 * 24 * 60 * 60,
  year: 365 * 24 * 60 * 60
};

You can declare types for the keys and values in that object like so:

const unitsOfTime: { [unit: string]: number } = {
  millisecond: 1,
  second: 60,
  hour: 60 * 60,
  day: 24 * 60 * 60,
  month: 30 * 24 * 60 * 60,
  year: 365 * 24 * 60 * 60
};

@AlexOros
Copy link

Hey, if I do this my object will have the following shape when I hove over it:

const unitsOfTime: {
    [unit: string]: number;
}

so auto-complete is no longer available, am I missing something?

@dgreene1
Copy link

dgreene1 commented Sep 4, 2020

The workarounds provided here are not recommended for Object.keys. If you really want to iterate over known values on an object, you can't know that the object doesn't have additional properties at runtime. I've provided a TypeScript Playground example that demonstrates the runtime error that can occur.

@codemaster101
Copy link

In addition to the answers, people could also try to use interfaces :)
Here is an example, hope it helps:

export interface IMapping {
  [propertyName: string]: string;
}

const mapping: IMapping = {
  apples: '$5',
  oranges: '$4',
}

// some code

const fruit: string = await getFruitFromInput();
const price: string = mapping[fruit];
return price;

@mnowotnik
Copy link

In addition to the answers, people could also try to use interfaces :)
Here is an example, hope it helps:

export interface IMapping {
  [propertyName: string]: string;
}

const mapping: IMapping = {
  apples: '$5',
  oranges: '$4',
}

// some code

const fruit: string = await getFruitFromInput();
const price: string = mapping[fruit];
return price;

Unfortunately, weakly typed solutions make autocompletion no longer work.

@sylann
Copy link

sylann commented May 12, 2021

I am a bit confused by this specific type checking.
It seems to me that:

  1. it is not actually protecting anything (people come up with hacks to bypass the error)
  2. it does not match the reality of javascript where accessing a missing key returns undefined.

Going further with the example above:

const unitsOfTime = {
  millisecond: 1,
  second: 60,
  hour: 60 * 60,
  day: 24 * 60 * 60,
  month: 30 * 24 * 60 * 60,
  year: 365 * 24 * 60 * 60
};

unitsOfTime[someStringInput] // Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '...'

I see 3 ways of bypassing this:

// Adding a looser type:
const unitsOfTime: { [unit: string]: number } = {...}
// wrong because it makes it possible to also use a string in cases where I might not want to accept a string that is not specific.

// Augmenting the type of the indexing key:
const time = unitsOfTime[someStringInput as keyof typeof unitsOfTime]
// even worse because it communicates the idea that the key **IS** one of unitsOfTime even though it might not be.
// Typescript won't tell the difference but there is a case where things will break.

// Addind a looser type at indexing site:
const time = (unitsOfTime as Record<string, number>)[someStringInput]
// ok, but it shouldn't be necessary...

...What I would expect:
As far as I know, accessing a key that does not exist in javascript will always return undefined.
So in my opinion, accessing such key should actually emit a type that is a union of undefined and all other values in the object.
But certainly not 'any'.

This way I could do;

const unitsOfTime = {...}

const time = unitsOfTime[someStringInput] // type of time is: number | undefined
if (!time) {
  // handle missing unit
}
// type of time is: number
// continue with time

@HIMA-MB02
Copy link

HIMA-MB02 commented Aug 22, 2021

For those of you having this issue with typescript:

// error

export interface IFilterData {
    categories: string[];
    genders: string[];
    brands: string[];
    discounts: string[];
}
const selectedFilters: IFilterData = someInitialValues;
for (let key in selectedFilters) {
    // you will get the error here
    selectedFilters[key].map(filter => {});
}

Like @alexandermckay said, we need to specify that the current record's keys are of type "string"
//fix

export interface IFilterData extends Record<string, any> {
    categories: string[];
    genders: string[];
    brands: string[];
    discounts: string[];
}

So extend your interfaces with Record<string, any>, or generic types to get rid of any.

Hope this helps.

@codemaster101
Copy link

Unfortunately, weakly typed solutions make autocompletion no longer work.

The idea would be @mnowotnik, that you use this interface for an object which can have changing key names, or dynamically set key names, during run-time.

data["example key name coming from for loop"]: "hey michael"

If people are using this solution over a simple interface then it could be a faulty implementation or just bad code practice. This solution is not to be used to help in writing code faster, but so that people do not use ts-ignore for their dynamic objects.

@sghabbour
Copy link

sghabbour commented Dec 16, 2021

@pke I have the same case? could you solve it?

@madebr
Copy link

madebr commented Feb 4, 2022

The following snippet also causes this error to be emitted:

  let some_mapping = {"key1": "value1", "key2": "value2", "key3": "value3"};
  let v = "key1";
  if (v in some_mapping) {
    let a = some_mapping[v];
    console.log("a is ${a}");
  }

Shouldn't the inferred type of v inside the if branch become: string | "key1" | "key2" | "key3"?
Or something similar.

@microsoft microsoft locked as resolved and limited conversation to collaborators Feb 4, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests