2

In regualr javascript I can do...

var array1 = ['one', 'two', 'three', 'four'];
var object = {
  one: 'apple',
  two: 'orange',
  three: 'peach',
  four: 'kiwi',
}

const map1 = array1.map(x => `${object[x]}`);

console.log(map1); // ["apple", "orange", "peach", "kiwi"]

BUT In react with TypeScript I want to do similar in JSX

<ul>
   { state.array1.map( (arrayItem:string, index:number) => (<li key={index} >{state.object[arrayItem]}</li>)) }
</ul>

The type script warning is:

Element implicitly has an 'any' type because expression of type 'any' can't be used to index type '{ one: string; two: string; three: string; four: string;

This is how I declared the object

const object: {
    one: string;
    two: string;
    three: string;
    four: string;
}

and the array when I mouse over it shows as (property) array1: never[]

when I save array1 to state it looks like

 const [state, setState] = useState({ array1: []}); 
4
  • can you show us how you've declared array1 and object? Commented Oct 4, 2019 at 13:46
  • @Ahmad the first two lines? Commented Oct 4, 2019 at 13:47
  • @VLAZ OP said that that's how they would do it in regular JavaScript, but they're using typescript. Commented Oct 4, 2019 at 13:48
  • 1
    @Ahmad and that's what you get if you use this code in TypeScript Commented Oct 4, 2019 at 13:49

2 Answers 2

4

The problem is that object only has keys one, two, three and four

You are slightly misreading the warning. There are actually two things here, the first one is

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ one: string; two: string; three: string; four: string; }'.

Which simply warns you that object[x] is not guaranteed to give you any of its current keys one, two, three, or four, so whatever it fetches will be assumed to be any type. This basically covers the situation object["five"] - since TS does not know of the existence of this property, it cannot guess what the type of it would be - it might be a number, or a string, or simply undefined.

No index signature with a parameter of type 'string' was found on type '{ one: string; two: string; three: string; four: string; }'.

This is what is stopping you. Again, TS only knows of the properties one, two, three, and four, however it cannot know that your array contains only those. When you haven't explicitly said what the array contains, it would try to infer the best common type which in this case is string. And since a string is not guaranteed to be any of one, two, three, and four (e.g., it might be five), then TS cannot guarantee what you do is safe.

You can explicitly say that your array only holds variables that are viable keys:

var array1: Array<'one' | 'two' | 'three'| 'four'> = ['one', 'two', 'three', 'four'];
var object = {
  one: 'apple',
  two: 'orange',
  three: 'peach',
  four: 'kiwi',
}

const map1 = array1.map(x => `${object[x]}`);

console.log(map1);

Check on the TypeScript Playground

Furthermore, if you formally declare what object's type would be, you can use keyof instead of manually listing all keys:

interface MyObject {
  one: string;
  two: string;
  three: string;
  four: string;
}

var array1: Array<keyof MyObject> = ['one', 'two', 'three', 'four'];

var object: MyObject = {
  one: 'apple',
  two: 'orange',
  three: 'peach',
  four: 'kiwi',
}

const map1 = array1.map(x => `${object[x]}`);

console.log(map1);

Check on the TypeScript Playground

Although you could be a bit more informal and not declare a specific type for object. This would lead to the following code which will check what the inferred type of object is and then make an array of all keys:

var array1: Array<keyof typeof object> = ['one', 'two', 'three', 'four'];

var object = {
  one: 'apple',
  two: 'orange',
  three: 'peach',
  four: 'kiwi',
}

const map1 = array1.map(x => `${object[x]}`);

console.log(map1);

Check on the TypeScript Playground

Your best options are the second and third. The first one is really just there to highlight why it doesn't work. keyof is interchangeable with 'one' | 'two' | 'three'| 'four' in this code but it has the benefit of not needing to update multiple places if object's definition changes.

Sign up to request clarification or add additional context in comments.

2 Comments

Note that for the first example you can use the as const keyword (TS 3.4+): var array1 = ['one', 'two', 'three', 'four'] as const;
I added a link to a sandox showing the issue a bit more clearly
0

The problem is that array1 is considered as an array of string (string[]) but object does not accept all strings as a key.

Either you need to

  • define array1 as an array of all the possible keys
type PossibleKey = 'one' | 'two' | 'three' | 'four';
var array1: PossibleKey[] = ['one', 'two', 'three', 'four'];
var object = {
  one: 'apple',
  two: 'orange',
  three: 'peach',
  four: 'kiwi',
}
const map1 = array1.map(x => `${object[x]}`);
  • make object accept all strings as a key
var array1 = ['one', 'two', 'three', 'four'];
var object: Record<string, string> = {
  one: 'apple',
  two: 'orange',
  three: 'peach',
  four: 'kiwi',
}
const map1 = array1.map(x => `${object[x]}`);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.