The data type 'null' is not a valid index type to be used in the Array.reduce() accumulator

This is a follow-up inquiry from: How can JavaScript convert multiple key-value pairs in object lists into one nested object?

The initial objective was to merge numerous objects with various key-value pairs into a single nested object.

For example, starting with this:

const items = [
        {
            "id": 3,
            "orgId": 2,
            "mod": "toyota",
            "part": "wheel",
            "price": 333
        },
        {
            "id": 4,
            "orgId": 2,
            "mod": "toyota",
            "part": "shell",
            "price": 350
        },
        {
            "id": 9,
            "orgId": 2,
            "mod": "honda",
            "part": "wheel",
            "price": 222
        },
        {
            "id": 10,
            "orgId": 2,
            "mod": "honda",
            "part": "shell",
            "price": 250
        }
    ]


and converting it to:


items = {
    "toyota": {"wheel": 333, "shell": 350 }, 
    "honda": {"wheel": 222, "shell": 250 }
}

The provided JavaScript code successfully achieves this:

const transformedItems = items.reduce((acc, item) => {
  acc[item.mod] = { ...acc[item.mod], [item.part]: item.price }
  return acc
}, {})

console.log(transformedItems)

Now, I am looking to implement this logic on the server-side using TypeScript, but encountering compilation errors:

/Users/john/tmp/dolphin/api/node_modules/ts-node/src/index.ts:293
    return new TSError(diagnosticText, diagnosticCodes)
           ^
TSError: ⨯ Unable to compile TypeScript:
src/utils/billingFunctions.ts:52:11 - error TS2538: Type 'null' cannot be used as an index type.

52       acc[item.mod] = { ...acc[item.mod], [item.part]: item.price }
             ~~~~~~~~~~~~~
.
.
. (additional error lines omitted for brevity)
.
.
.

While investigating further, when executing:

const transformedItems = items.reduce((acc, item) => {

  console.log(acc, item.mod)  // log statement

  acc[item.mod] = { ...acc[item.mod], [item.part]: item.price }
  return acc
}, {})

The output from the console log displays normally: item.mod being recognized as a string. Why then does the compiler generate the error message about it being of type 'null'?

Answer №1

When working with the reduce method, it is common to manually specify the type of the accumulated value. This is especially necessary when supplying an empty object like {}. There are two approaches to handling this situation: either include a type assertion in the parameter or utilize the generic parameter of the function:

const transformedItems = items.reduce<Record<string, Record<string, number>>>(
    (acc, item) => {
        acc[item.mod] = { ...acc[item.mod], [item.part]: item.price }
        return acc
    }, {})

If the type information of the items variable is not known at compile time, you may need to explicitly define its structure using an interface, for example:

interface Item
{
    id: number;
    orgId: number;
    mod: string;
    part: string;
    price: number;
}

This interface can then be used directly within the code or declared before the reduce operation:

const transformedItems = (items as Item[]).reduce(...

Answer №2

One potential solution could involve coercing the item.mod value to ensure TypeScript recognizes it as a string. This can be achieved by wrapping it with String(...), like this:

const updatedItems = items.reduce((accumulator, item) => {

  console.log(accumulator, item.mod)  // output

  accumulator[item.mod] = { ...accumulator[String(item.mod)], [item.part]: item.price }
  return accumulator
}, {})

This approach might not align perfectly with TypeScript conventions.

Answer №3

Ensure that the variable items is an array of type Item, not just any generic type. In the provided example, TypeScript can infer that all items within the array have a mod field that is a string based on the given examples. However, if you are passing items as arguments to a function or dynamically populating the array, it is important to explicitly define the type of the items.

By explicitly defining the type, TypeScript will be certain that each item in the array contains both a mod and a part field, both of which are strings.

type Item = {
    id: number;
    orgId: number;
    mod: string;
    part: string;
    price: number;
};

function getTransformedItems(items: Item[]) {
    return items.reduce((acc, item) => {
        acc[item.mod] = { ...acc[item.mod], [item.part]: item.price };
        return acc;
    }, {} as Record<string, Record<string, number>>);
}

Answer №4

A different approach, more traditional in nature

Introducing data types to simplify the transformation:

interface CommonData {
    id: number;
    orgId: number;
    mod: string;
    part: string;
    price: number;
}

interface PartDetails {
    name: string;
    price: number;
}

interface TransformedData {
    carModel: string;
    details: Array<PartDetails>
}

Transformation process:

function transformData(data: Array<CommonData>): Array<TransformedData> {
    const transformedData: Array<TransformedData> = [];

    data.forEach((item: CommonData) => {
        const index = transformedData.findIndex(transformedItem => transformedItem.carModel === item.mod);
        
        if (index === -1) {
            transformedData.push({
                carModel: item.mod,
                details: [
                    {
                        name: item.part,
                        price: item.price
                    }
                ]
            })
        } else {
            const existingTransform = transformedData[index];

            existingTransform.details.push({
                name: item.part,
                price: item.price
            });
        }
    });

    return transformedData;
}

An Example

Input Data

[
    {
        "id": 3,
        "orgId": 2,
        "mod": "toyota",
        "part": "wheel",
        "price": 333
    },
    {
        "id": 4,
        "orgId": 2,
        "mod": "toyota",
        "part": "shell",
        "price": 350
    },
    {
        "id": 9,
        "orgId": 2,
        "mod": "honda",
        "part": "wheel",
        "price": 222
    },
    {
        "id": 10,
        "orgId": 2,
        "mod": "honda",
        "part": "shell",
        "price": 250
    }
]

Output Data

[
    {
        "carModel": "toyota",
        "details": [
            {
                "name": "wheel",
                "price": 333
            },
            {
                "name": "shell",
                "price": 350
            }
        ]
    },
    {
        "carModel": "honda",
        "details": [
            {
                "name": "wheel",
                "price": 222
            },
            {
                "name": "shell",
                "price": 250
            }
        ]
    }
] 

Answer №5

After analyzing the error message, it appears that there is a missing value in the item.mod field within a record.

The error "Type 'null' cannot be used as an index type for acc[item.mod]" suggests an issue with using null as an index.

The appropriate solution will depend on how you plan to handle such records.

If you wish to preserve them, consider converting the item.mod to a String as recommended by other responses.

If you prefer to exclude them, implement a conditional check as shown below:

const transformedItems = items.reduce((acc, item) => {
  if (item.mod) {
    acc[item.mod] = { ...acc[item.mod], [item.part]: item.price }
  }
  return acc
}, {})

In either scenario, it seems that the data being handled is inconsistent and should undergo validation before usage.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Angular2 allows for the firing of all columns in one click using *ngFor

<tr *ngFor = 'let student of students> <td contenteditable="true" class ='phone' #button> {{student.phone}} <i (click)='showbox()' class = ' glyphicon glyphicon-edit'></i> <input *ngIf=&apo ...

After integrating session store into my application, nestjs-sequelize is not synchronizing with any models

I'm currently working on developing a Discord bot along with a website dashboard to complement it. Everything is running smoothly, except for the backend Nestjs API that I am in the process of creating. I decided to use Sequelize as the database for m ...

Displaying decimal values in Angular as percentages

In my Angular application, I have a numeric textbox that displays a percentage value and allows users to update it. https://i.stack.imgur.com/eCOKe.png <label for="fees">Fees %</label> <div class="inpu ...

Gatsby struggles with generating Contentful pages using TypeScript

I have been working on creating dynamic pages with Contentful in Gatsby + Typescript. While I am able to fetch data successfully using GraphQL in a browser, I encounter issues when trying to fetch data in gatsby-node.ts. The pages seem to be generated part ...

Declare the variable as a number, yet unexpectedly receive a NaN in the output

I'm facing an issue with a NaN error in my TypeScript code. I've defined a variable type as number and loop through an element to retrieve various balance amounts. These values are in the form of "$..." such as $10.00 and $20.00, so I use a repla ...

What could be causing the cyclic dependency problem after upgrading to Angular 9?

I am experiencing an issue with a specific file containing the following code: import { Injectable } from '@angular/core'; import { I18n } from '@ngx-translate/i18n-polyfill'; import { isNumber } from 'lodash'; import { Confir ...

Steps to resolve the error message 'Argument of type 'number' is not assignable to parameter of type 'string | RegExp':

Is there a way to prevent users from using special symbols or having blank spaces without any characters in my form? I encountered an error when trying to implement this in my FormGroup Validator, which displayed the message 'Argument of type 'nu ...

Categorize items based on their defined attributes using Typescript

[origin object array and expect object array ][1] origin object array: 0: amount: 100000000000000000000 feeTier: 0.3 price: 00000 priceDecimal: 0000 status: "unknown" tokenXAddr: "0x*********" tokenXSymbol: "USDC" tokenYAddr: ...

How can we optimize ternary statements within ternary statements in Type Script and React Native for best practices?

Can you help me optimize this code snippet that uses nested ternary operators for better readability and correctness? <TouchableOpacity style={ darkMode ? filterState === 'A' ? styles.activeButtonDark : styles.buttonDa ...

Guide to setting up an interface for returning events within a parameter

I am working with a component that has the following interface: interface IPreTicketListProps { onEditPreTicket: (preTicketId: string) => { preTicketId: string }; onCreateSuggestedOperation: (preTicketId: string) => { preTicketId: string }; } ...

What is the best method for showcasing this console.log information in an Angular application?

I have developed a feature that displays users who are online. While it works perfectly in the console log, I am struggling to show p as the result if the user is online. Below is the code snippet: ngOnInit() { this.socket.emit('online', { r ...

Understanding how the context of an Angular2 component interacts within a jQuery timepicker method

Scenario: I am developing a time picker component for Angular 2. I need to pass values from Angular 2 Components to the jQuery timepicker in order to set parameters like minTime and maxTime. Below is the code snippet: export class TimePicker{ @Input() ...

The onChange event for React input does not trigger when the value remains the same

Script: function SingleInput(props: {value: string; onChanged: (value: string) => void}) { const handleChange = useCallback( (e: React.ChangeEvent<HTMLInputElement>) => { const newValue = e.target.value; cons ...

Exploring the possibilities with Rollbar and TypeScript

Struggling with Rollbar and TypeScript, their documentation is about as clear as AWS's. I'm in the process of creating a reusable package based on Rollbar, utilizing the latest TS version (currently 4.2.4). Let's delve into some code snipp ...

The data source retrieved through the "get" API method is missing from the mat-table

Recently, I've started working with angularCLI and I'm facing an issue in creating a table where the dataSource is fetched from a fake API. Let me share my component class: import { Component, OnInit } from '@angular/core'; import { Fo ...

Issue with data not being recorded accurately in React app after socket event following API call

The Application Development I have been working on an innovative app that retrieves data from the server and visualizes it on a chart while also showcasing the total value in a Gauge. Additionally, the server triggers an event when new data is stored in t ...

Problems with installing ambient typings

I'm having trouble installing some ambient typings on my machine. After upgrading node, it seems like the typings are no longer being found in the dt location. Here is the error message I am encountering: ~/w/r/c/src (master ⚡☡) typings search mo ...

What is the best way to populate empty dates within an array of objects using TypeScript or JavaScript?

I am trying to populate this object with dates from today until the next 7 days. Below is my initial object: let obj = { "sessions": [{ "date": "15-05-2021" }, { "date": "16-05-2021" }, { "date": "18-05-2021" }] } The desired ...

When using Angular, it is important to remember that calling `this.useraccount.next(user)` may result in an error stating that an argument of type 'HttpResponse<any>' cannot be used with a 'Useraccount' balance

When attempting to use this.useraccountsubject(user) to insert information upon login, I encountered an error: ErrorType: this.useraccount.next(user) then Error An argument of type 'HttpResponse' is not allowed against a balance of 'Userac ...

Access to the server has been restricted due to CORS policy blocking: No 'Access-Control-Allow-Origin'

I’m currently encountering an issue with displaying API content in Angular and I’m at a loss on how to troubleshoot it and move forward. At this moment, my main objective is to simply view the URL data on my interface. Does anyone have any insights or ...