Utilizing the reduce() function to simultaneously assign values to two variables from data input

Looking to simplify the following function using reduce(), as the operations for variables selectedEnrolled and selectedNotEnrolled are quite similar.

I attempted to use map(), but since I wasn't returning anything, it led to unintended side effects which I now understand is not good coding practice.

updateSelectionCounts(selected: any) {
  this.selectedNotEnrolled = selected.reduce((count, id) => {
    return this.hasSteps(id)
      ? count + (this.userShelf.courseIds.indexOf(id) === -1 ? 1 : 0)
      : count;
  }, 0);
  this.selectedEnrolled = selected.reduce((count, id) => {
    return this.hasSteps(id)
      ? count + (this.userShelf.courseIds.indexOf(id) === -1 ? 0 : 1)
      : count;
  }, 0);
}

Your assistance is greatly appreciated.

Answer №1

The main distinction seems to be in the increment value returned, depending on whether the current id is present in the this.userShelf.courseIds array.

countSelectNotEnrolled(selected: any) {
  this.selectedNotEnrolled = selected.reduce((count, id) => {
    return this.hasSteps(id)
      ? count + (this.userShelf.courseIds.indexOf(id) === -1 ? 1 : 0)
      //                                                       ^^^^^
      : count;
  }, 0);
  this.selectedEnrolled = selected.reduce((count, id) => {
    return this.hasSteps(id)
      ? count + (this.userShelf.courseIds.indexOf(id) === -1 ? 0 : 1)
      //                                                       ^^^^^
      : count;
  }, 0);
}

A more general approach to refactoring such patterns involves extracting the difference as a dynamic aspect during function creation.

You can create a higher-order function that returns another function, taking the increment value as an argument. This way, you can create two functions for summing counts, one for selectedNotEnrolled and the other for selectedEnrolled.

Note: Using Array.prototype.includes is cleaner than Array.prototype.indexOf when checking array membership.

function createEnrolmentSum( // Accepts dynamic setup logic as arguments
  incrementIfSelected,
  incrementIfUnselected = incrementIfSelected === 1 ? 0 : 1
) {
  return function (selected) { // <--- Returns a function with common logic
    return selected.reduce((count, id) => {
      return this.hasSteps(id)
        ? count +
            (this.userShelf.courseIds.includes(id) // <-- Cleaner includes method
              ? incrementIfSelected
              : incrementIfUnselected)
        : count;
    }, 0);
  };
}

// ...

// Instance methods for proper `this` binding
getEnrolledSum = createEnrolmentSum(1); 
getNotEnrolledSum = createEnrolmentSum(0);

countSelectNotEnrolled(selected: any) {
  this.selectedNotEnrolled = this.getNotEnrolledSum();
  this.selectedEnrolled = this.getEnrolledSum();
}

This demonstration shows how similar code could be refactored. The code is not very readable due to using bare numbers like `1` or `0`:

// Lack of readability due to unclear integer values
getEnrolledSum = createEnrolmentSum(1);
getNotEnrolledSum = createEnrolmentSum(0);

To improve clarity, you may use a configuration object:

getEnrolledSum = createEnrolmentSum({
  incrementIfSelected: 1,
  incrementIfUnselected: 0
});
getNotEnrolledSum = createEnrolmentSum({
  incrementIfSelected: 0,
  incrementIfUnselected: 1
});

However, the complexity remains high despite being DRY. For your situation, computing both sums in a single loop might be a better starting solution for improved performance and simplicity:

countSelectNotEnrolled(selected) {
  let selectedNotEnrolled = 0,
      selectedEnrolled = 0;

  for (const id of selected) {
    if (this.hasSteps(id)) {
      if (this.userShelf.courseIds.includes(id)) {
        selectedEnrolled += 1;
      } else {
        selectedNotEnrolled += 1;
      }
    }
  }

  this.selectedNotEnrolled = selectedNotEnrolled;
  this.selectedEnrolled = selectedEnrolled;
}

If still preferring array reduction, an object to carry variables through iterations can reduce nesting:

countSelectNotEnrolled(selected) {
  const { selectedNotEnrolled, selectedEnrolled } = selected.reduce(
    (result, id) => {
      if (this.hasSteps(id)) {
        if (this.userShelf.courseIds.includes(id)) {
          result.selectedEnrolled += 1;
        } else {
          result.selectedNotEnrolled += 1;
        }
      }
      return result;
    },
    { selectedNotEnrolled: 0, selectedEnrolled: 0 }
  );

  this.selectedNotEnrolled = selectedNotEnrolled;
  this.selectedEnrolled = selectedEnrolled;
}

For increased readability, filter out ids without steps first:

countSelectNotEnrolled(selected) {
  const { selectedNotEnrolled, selectedEnrolled } = selected.filter(this.hasSteps).reduce(
    (result, id) => {
      if (this.userShelf.courseIds.includes(id)) {
        result.selectedEnrolled += 1;
      } else {
        result.selectedNotEnrolled += 1;
      }
      return result;
    },
    { selectedNotEnrolled: 0, selectedEnrolled: 0 }
  );

  this.selectedNotEnrolled = selectedNotEnrolled;
  this.selectedEnrolled = selectedEnrolled;
}

Utilizing length of filtered arrays for enrolled and not-enrolled ids provides a concise alternative:

countSelectNotEnrolled(selected) {
  const selectedWithSteps = selected.filter(this.hasSteps);

  this.selectedNotEnrolled = selectedWithSteps.filter(
    id => !this.userShelf.courseIds.includes(id)
  ).length;

  this.selectedEnrolled = selectedWithSteps.length - this.selectedNotEnrolled;
}

Alternatively, expressing enrolled and not-enrolled counts intertwined offers a simplified approach:

countSelectNotEnrolled(selected) {
  const selectedWithSteps = selected.filter(this.hasSteps);

  this.selectedNotEnrolled = selectedWithSteps.filter(
id => !this.userShelf.courseIds.includes(id)
  ).length;

  this.selectedEnrolled = selectedWithSteps.length - this.selectedNotEnrolled;
}

In conclusion, prioritize code readability over specific patterns. JavaScript is meant for humans to understand easily, minimizing cognitive load🙂

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

Setting up a Variable with an Object Attribute in Angular

I am attempting to create a variable that will set a specific property of an object retrieved through the get method. While using console.log in the subscribe function, I am able to retrieve the entire array value. However, as a beginner, I am struggling ...

How can I pass a value from a jQuery variable to a PHP variable?

Utilizing jQuery to create a table based on the output of JSON. The JSON values are retrieved from a SoapClient, which is functioning correctly and producing the desired output. https://i.sstatic.net/vJbfW.png Now, the goal is to assign the value of the ...

Creating a custom function in JavaScript to interact with the `windows.external` object specifically for use

In my current project, I am facing an issue with JavaScript file compatibility across different browsers. Specifically, I have a JavaScript file that calls an external function (from a separate file) using windows.external, like this: windows.external.se ...

The methods $.get() and $.ajax() in jQuery are failing to retrieve data from PHP

I've been trying to use a script to display a message from a PHP file after a click event, but I've run into issues with both jQuery $.get() and $.ajax(). With $.ajax(), I get an alert with an empty message, while $.get() alerts "null". load.php ...

Differentiate among comparable values through placement regex

I'm currently tackling a challenge involving regex as I work on breaking down shorthand CSS code for the font property. Here is my progress thus far: var style = decl.val.match(/\s*(?:\s*(normal|italic|oblique)){1}/i); style = style ? style ...

Whoops! Looks like there was a hiccup with the Vercel Deployment Edge Function, causing an

Every time I attempt to send a POST request to my Edge Function on Vercel Deployment, I encounter the following error message: [POST] /api/openai reason=EDGE_FUNCTION_INVOCATION_FAILED, status=500, user_error=true TypeError: Illegal invocation at app/api/ ...

Eliminating Angular's @Injectable decorator in npm build process

I have encountered a setback while working on a small helper package for Angular. The issue I am facing is related to an exported class that serves as an Angular service and is decorated with @Injectable(). After running npm run build, the compiled class ...

Using typescript in react to define conditional prop types

Currently, I am utilizing react-select as my select component with the multi prop. However, my goal is to have the onChange argument set as an array of options when the multi prop is true, and as OptionType when false. To achieve this, I am implementing di ...

The ace.edit function is unable to locate the #javascript-editor div within the mat-tab

Having trouble integrating an ace editor with Angular material Error: ace.edit cannot locate the div #javascript-editor You can view my code on StackBlitz (check console for errors) app.component.html <mat-tab-group> <mat-tab label="Edito ...

VueJS emits a warning when filtering an array inside a for loop

I'm encountering an issue with my filtering function in VueJS. While it works fine, I am seeing a warning message in the console. You can check out this example to see the problem. The problem arises when I need to filter relational data from a separ ...

Merge identical year items into a single entity

I have an array of objects containing car data: [ { year: 2019, company: 'Toyota', quantity: '450' }, { year: 2019, company: 'Ford', quantity: '600' }, { year: 2020, company: ...

Is there a way to retrieve a comprehensive list of all anchors that contain the data-ng-click attribute

I am currently working on a web application built with asp.net and angularjs. My goal is to specifically retrieve all anchor tags that have the attribute data-ng-click assigned to them. Is there a way to achieve this? If so, how can it be done? I a ...

Tips for utilizing multiple ngFor directives for property binding within a single directive

After implementing the ng-drag and drop npm module with the draggable directive, I encountered an issue while trying to display a list of items from a 2D array using li elements. Since multiple ngFor's are not allowed in Angular, I needed to come up w ...

The identifier 'name' is not found in the specified data type

How can I resolve the error 'Property 'name' does not exist on type' in TypeScript? Here is the code block : **Environment.prod.ts** export const environment = { production: true, name:"(Production)", apiUrl: 'https://tes ...

Encountering the error message "Unable to instantiate User: constructor not found" while testing API functionality for

Currently, I am in the process of integrating my backend with MongoDB ATLAS. In order to achieve this, I am utilizing express, mongoose, and node.js for testing purposes. Specifically, I am focusing on testing my API routes, such as adding a user. Below i ...

What are the issues causing trouble for my modules, services, and more in Angular ^17?

As I was going through the themes, I couldn't find a similar question. My issue revolves around Angular's inability to locate modules and services that are created using "ng g". Everything seems to be correctly set up, but errors or warnings keep ...

Is it possible to create a channel list using the YouTube API if I am not the owner of the channel? I am not getting any errors, but nothing is showing up

I am currently working on creating a channel list and playlist of videos from a Music channel that I do not own. Here is the link to the channel: https://www.youtube.com/channel/UC-9-kyTW8ZkZNDHQJ6FgpwQ/featured. My goal is to extract data from this channe ...

What are the typical scenarios where Angular's DI resolution modifiers are used?

Recently, I delved into the world of resolution modifiers, but I am struggling to grasp their practical application in my project. It appears that they are mainly used when dealing with services and requiring different instances of them in various parts of ...

Unleashed Breakpoint Mystery in Ionic 5 Angular with VSCode

I recently upgraded my Ionic 5 Angular 12 app from Ionic 4 Angular 8. The application is working well and remains stable, but I have encountered some issues while debugging. Firstly, when I use the launch.json file in Visual Studio Code to run the app, it ...

Struggling to make type definitions work in react-hook-form 7

Upon defining the types of my form fields, I encountered an issue where each field seems to take on all three different types. This is puzzling as I have specified 3 distinct types for my 3 different fields. What could be causing this confusion? https://i ...