10

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

Bethany
  • 1,003
  • 5
  • 12
  • 18
  • Not a typescript guy but would guess that it's because you don't declare the third and fourth arguments to the `reduce` callback. The function you pass to `reduce` gets 1. The accumulator. 2. The current item in the array. 3. The current index. 4. The original array. – Jared Smith Feb 13 '18 at 18:20
  • Set `noImplicitAny` to false in your tsconfig.json file. – HaveSpacesuit Feb 13 '18 at 18:22

5 Answers5

18

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.

CRice
  • 29,968
  • 4
  • 57
  • 70
5

The correct types for your array would be:

Array<{time: string}>

or:

{time: string}[]

or:

{[key: number]: {time: string}}
Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
1

Answer to Original Question

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)

Other Possible Caveats

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);

Reference: Corrected Code from the Original Question

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] };
});
Kirill Taletski
  • 388
  • 2
  • 6
0

If I'm understanding you, you need to create the interface for the object...

interface counts_t{
    time?: string,
    messages?: number
}

var data: counts_t[] = [
    { time: '9:54' }, 
    { time: '9:55' }, 
    { time: '9:56' }, 
    { time: '9:57' }
];

    var counts = data.reduce((newArray:counts_t, item:counts_t) => {
       let time = item.time;
       if (!newArray.hasOwnProperty(time)) {
           newArray[time] = 0;
       } 
       newArray[time]++;
       return newArray;
    }, {});

    console.log(counts);

    var countsExtended: counts_t[] = Object.keys(counts).map(k => {
       return { time: k, messages: counts[k] };
    });

   console.log(countsExtended);
I wrestled a bear once.
  • 22,983
  • 19
  • 69
  • 116
  • Thanks 'I wrestled a bear once'. That may be another way to solve it. CRice's answer above worked. Awesome username btw – Bethany Feb 13 '18 at 18:58
0

You have to add a type that has an index signature. Ex.

const myObj: {[key: string]: string} = {
  someProperty: 'someValue',
};

The {[key: string]: any} is telling Typescript that this obj has an index signature of type string.

charri
  • 984
  • 7
  • 9