javascripttypescript

Is there a fundamental problem with this arrow function


I'm an Objective-C/Swift developer debugging an issue in a React Native app, and I suspect there's something not right somewhere in the React Native code. I didn't write this and don't know Typescript/Javascript, but this code looks like it could perhaps be causing an issue. Are there any potential issues with this function:

const getCurrentMonthData = (monthlySummary: monthlyData) => {
    const currentMonthData = monthlySummary.find(
      data =>
        parseInt(data.monthNumber) === month && parseInt(data.year) === year,
    );
     logV(TAG, `getCurrentMonthData type for currentMonthData: $typeof(currentMonthData)`);
    return currentMonthData;
  };

VisualStudioCode says "property find doesn't exist on type monthlyData", which it doesn't:

export interface monthlyData {
  monthNumber: number,
  month: string,
  year: number,
  dailyData: {},
  monthlySpamMin: monthlySpam,
  monthlySpamMax: monthlySpam,
  monthlyBlockedSpam: number,
  monthlySpamTotal: number,
  spamFreeStreak: number,
}

This code is working most of the time, however I added the log line and most of that time its logging 'object' but sometimes its 'undefined'.

Where the function is called there is a warning from Visual Studio Code saying

"Argument of type '[] | monthlyData[]' is not assignable to parameter of type 'monthlyData'. Type '[]' is not assignable to type 'monthlyData'.ts(2345)"

Its called here:

var currentMonthData = getCurrentMonthData(data.monthlySummary);

And this is the type:

 monthlySummary: monthlyData[] | []

My question is if there is a problem with this code, then why does the code work the majority of the time, as opposed to not working at all, all of the time?

If find doesn't exist, why doesn't it crash at run time?


Solution

  • Yes, there is a fundamental problem with that function. As you say, monthlyData doesn't have a find function. The code is expecting an array of monthlyData instances, which is monthlyData[]. (find finds the first element in the array that matches.) If you change the type to that, both that error and the one calling the function should go away.

    This code is working most of the time...

    That's because of the other error you mentioned: "Argument of type '[] | monthlyData[]' is not assignable to parameter of type 'monthlyData'" Apparently, when the function is called, it's actually being passed the right kind of thing (an array), it's just that the type information for the function is wrong. On your project, you're clearly not using the TypeScript compiler itself to compile the TypeScript to JavaScript, or it would be failing on those errors and not outputting any JavaScript. (That's not unusual, bundlers can compile TypeScript directly these days, and often do so faster than TypeScript itself does.)

    ...however I added the log line and most of that time its logging 'object' but sometimes its 'undefined'...

    typeof returns "object" for arrays, so when you're seeing "object", that's most likely when it's working.

    It'll fail when the value is undefined, though. The function isn't written to allow undefined.

    Your best bet (IMHO) is to find the parts of the code that are calling the function where the value is undefined, and determine whether they should be calling the function when they don't have an array to pass to it.

    If you decide that yes, it's valid that they pass it undefined, then update the function like this:

    const getCurrentMonthData = (monthlySummaries: monthlyData[] | undefined) => {
        // −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−v
        const currentMonthData = monthlySummaries?.find(
            (data) =>
                parseInt(data.monthNumber) === month && parseInt(data.year) === year
        );
        return currentMonthData;
    };
    

    That's conditional chaining. When monthlySummaries (note I changed it to the plural, since it's an array), the find call won't occur and currentMonthData will be set to undefined. (It will also be set to undefined if there is no match.)

    But that's a bit pointless. You might decide instead that whatever is calling the function when it only has undefined to pass in should just not call the function instead. In that case, just add the [] to the type so the function correctly identifies that it's expecting an array (and update the code calling with the value undefined so it doesn't call the function).


    Side notes:

    1. Another problem with the code is that it's using parseInt on things that are already of type number. parseInt is for converting strings to integer numbers, calling it on a number is silly. Sometimes people do that to truncate fractional numbers [it usually works, because it implicitly converts the number to string, then converts that string back to an integer, throwing away the fractional part], but to do that, they should be using Math.trunc.

    2. The standard convention in TypeScript code is that type names other than the built-in primitive type names should start with an initial capital letter, so interface MonthlyData rather than interface monthlyData.

    3. If the places that call the function with undefined don't currently have errors, it means that that code has incorrect type information, undefined values are creeping in where the type system has been told there won't be any. That'll be ... fun ... to fix. You'll have to find each place and update it accordingly. (How you update it will be very specific to that code, so I can't help you with that.)

    4. Re:

      And this is the type:

      monthlySummary: monthlyData[] | []
      

      That looks like another type error, it's extremely rare that it's correct to have an array of type [] (no element information). So that, too, is probably worth tracking down as the type there should probably just be monthlyData[] (or rather, MonthlyData[]).