I am trying to use reduce with Typescript to reach a total count of incoming messages. I'm confused on how to add an index signature. I keep getting the error: " Element implicitly has an 'any' type because type '{}' has no index signature." on variables newArray and counts[k]. I have read through several similar questions but have not figured out how to apply them to my specific scenario. I am new to JavaScript and TypeScript.
Here is my array:
var data = [
{ time: '9:54' },
{ time: '9:54' },
{ time: '9:54' },
{ time: '9:55' },
{ time: '9:55' },
{ time: '9:55' },
{ time: '9:55' },
{ time: '9:56' },
{ time: '9:56' },
{ time: '9:56' },
{ time: '9:56' },
{ time: '9:57' },
{ time: '9:57' },
{ time: '9:57' },
{ time: '9:57' },
{ time: '9:57' }
];
This is how I need my array for use in a rechart graph:
var dataWithCounts = [
{ time: '9:54', messages: 3 },
{ time: '9:55', messages: 4 },
{ time: '9:56', messages: 4 },
{ time: '9:57', messages: 5 }
];
With the help of 'Just a Student's' answer from this Stack Overflow question: Group and count values in an array , I have the following.
var counts = data.reduce((newArray, item) => {
let time = item.time;
if (!newArray.hasOwnProperty(time)) {
newArray[time] = 0;
}
newArray[time]++;
return newArray;
}, {});
console.log(counts);
var countsExtended = Object.keys(counts).map(k => {
return { time: k, messages: counts[k] };
});
console.log(countsExtended);
Where and how do I declare an index signature? Below are various things I've tried.
let newArray: { [time: string] };
and receive a duplicate identifier error.
adding string to the parameter var counts = data.reduce((newA:string, item)
gives me an error "Element implicitly has an 'any' type because index expression is not of type 'number'."
adding newA[time].toString()
gives me errors, "The left-hand side of an assignment expression must be a variable or a property access."
The type of your accumulator in the .reduce
call is almost certainly the issue. Since it is just given as {}
, that's what its type is inferred as, and the type {}
doesn't have an index signature. You can fix this by casting your initial value so that it does include a signature:
var counts = data.reduce((newArray, item) => {
let time = item.time;
if (!newArray.hasOwnProperty(time)) {
newArray[time] = 0;
}
newArray[time]++;
return newArray;
}, {} as {[key: string]: any}); // <-- note the cast here
I gave the index signature type any
, but you may want to make it something more specific for your particular case.
The main caveat was that the reduce
accumulator value was not properly typed.
Original code:
const result = data.reduce(someLogicCallback, {})
Need to do in the following way:
const accumulator: {[key: string]: number} = {}
const result = data.reduce(someLogicCallback, accumulator)
This part might be helpful for the later occasional googlers.
I came up here looking to solve a similar issue, but in my case I had an object with an already predefined type
typeof someObject // predefinedType
type predefinedType = {
someKey: number;
otherKey: number;
}
and I got index signature is missing in type predefinedType
error when I tried to extract entries from that object
const entries = Object.entries<number>(someObject);
The obvious solution was to do the type assertion
type TypedIndexObj = {[key: string]: number}
const entries = Object.entries<number>(someObject as TypedIndexObj);
But this would be too restrictive because it will always assert values types of someObject
as number
which might change over time.
The best way of adding index type of such an object that I came up with is to use keyof
syntax
type TypedIndexObj = {[key in keyof typeof someObject]:(typeof someObject)[key]}
const entries = Object.entries<number>(someObject as TypedIndexObj);
Here is the fully typed corrected version of the code from the original question:
const accumulator: {[key: string]: number} = {}
const counts = data.reduce((_accumulator, item) => {
const time = item.time;
// no error on the next line
if (!_accumulator.hasOwnProperty(time)) {
_accumulator[time] = 0;
}
_accumulator[time]++;
return _accumulator;
}, accumulator);
const countsExtended = Object.keys(counts).map(k => {
return { time: k, messages: counts[k] };
});
The correct types for your array would be:
Array<{time: string}>
or:
{time: string}[]
or:
{[key: number]: {time: string}}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With