Using Typescript to iterate through an array of objects and modifying their keys using the forEach method

I have an object called 'task' in my code:

const task = ref<Task>({
  name: '',
  description: '',
  type: undefined,
  level: 'tactic',
  participants: undefined,
  stages: undefined,
});

export interface Task extends CommonEntity {
  description?: string;
  type?: TaskType;
  level?: EntityLevel;
  participants?: number;
  stages?: TaskTypeStage[];
  questions?: TaskQuestion[];
  materials?: TaskMaterial[];
  indicators?: TaskIndicator[];
  duration?: number;
  images?: [];
  program_id?: number;
  roles?: TaskRole[];
  tables?: TaskTable[];
  competences?: TaskCompetence[];
  task_type_id?: number;
}

export interface CommonEntity {
  id?: number;
  name?: string;
  created_at?: string;
  updated_at?: string;
  errors?: ApiErrors;
  delete?: boolean;
  isTemporaryIdAdded?: boolean;
}

In a method, I need to modify the task object.

function handleSubmit() {
    task.value.materials = removeTemporaryIdFromArray<TaskMaterial>(task.value.materials);

    task.value.questions = removeTemporaryIdFromArray<TaskQuestion>(task.value.questions);

    task.value.roles = removeTemporaryIdFromArray<TaskRole>(task.value.roles);

    task.value.tables = removeTemporaryIdFromArray<TaskTable>(task.value.tables);
}

export function removeTemporaryIdFromArray<T>(
  entity: TaskMaterial[] | TaskQuestion[] | TaskRole[] | TaskTable[] | undefined
) {
  if (entity) {
    return entity
      .filter((item) => !item.delete || !item.isTemporaryIdAdded)
      .map((item) => Object.assign({}, removeTemporaryId<T>(item)));
  }
}

export function removeTemporaryId<T>(item: CommonEntity): T {
  const { id, isTemporaryIdAdded, ...rest } = item;

  return isTemporaryIdAdded ? (rest as T) : { id, ...(rest as T) };
}

I am looking for a way to optimize the handleSubmit function without repeating the same code for each key. How can I improve this?

I tried using a forEach loop with an array of keys, but the value returned is always 'undefined'. Can you suggest a better approach?

(['materials', 'questions', 'roles', 'tables'] as (keyof typeof task.value)[]).forEach((key) => {
      task.value[key] = removeTemporaryIdFromArray<typeof key>(task.value[key] as TaskMaterial[]) as undefined;
    });

I'm aware that my current solution is not ideal. Do you have any recommendations on how to improve it?

Answer №1

I believe the pretension is inappropriate

task.value[key] = removeTemporaryIdFromArray<typeof key>(task.value[key] as TaskMaterial[]) as undefined;

due to the fact that removeTemporaryIdFromArray cannot determine between multiple types (TaskMaterial[] | TaskQuestion[] | TaskRole[] | TaskTable[] | undefined) when setting task.value[key].

I suggest using removeTemporaryIdFromArray_InPlace instead.

Below is my suggestion (it might be a bit verbose), and testing it without context can be challenging. However, TypeScript validates it.

// helper type
type Writable<T> = { -readonly [K in keyof T]: T[K] };
// properties to iterate over
const KeyNames = ["materials", "questions", "roles", "tables"] as const;
// type helper with only the 'good' keys, never undefined (but we do not care.)
type SubPropertyTask = Required<Pick<Task, typeof KeyNames[number]>>;
// transforming the properties into an array of keys for easy iteration
const WriteableKeyNames = KeyNames as Writable<typeof KeyNames> as Array<keyof SubPropertyTask>;

// new submission function
function handleSubmitV3() {
    // iterating over keys
    WriteableKeyNames.forEach((key: keyof SubPropertyTask) => {
        // calling the somewhat non-trivial function
        // < type of first element of the array (because we know its array)
        // also need to specify the type of the key, which cannot be inferred.
        removeTemporaryIdFromArrayInplace<SubPropertyTask[typeof key][0], typeof key>(task[key], key);
    });
}
// T might be written like this : T extends TaskQuestion | TaskMaterial | TaskRole | TaskTable
// but if you need to add other property, name should be enougth
export const removeTemporaryIdFromArrayInplace = <T, E extends keyof SubPropertyTask>(entity: Array<T> | undefined, e: E): void => {
    // custom filtering
    const filteredEntity = entity?.filter((taskObject: T) => taskObject);

    // assigning the new value, with the necessary cast
    // Because at this time typeof task[e] is same as T and as  SubPropertyTask[E] | undefined
    // ... I think.
    task[e] = filteredEntity as SubPropertyTask[E] | undefined;
};

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

Leveraging Selenium to dismiss a browser pop-up

While scraping data from Investing.com, I encountered a pop-up on the website. Despite searching for a clickable button within the elements, I couldn't locate anything suitable. On the element page, all I could find related to the 'X' to cl ...

Distinguishing between resolving a promise in a service/factory as opposed to in a controller within AngularJS

After experimenting with resolving a promise in both a service and a controller, I have found that I prefer to resolve it in the service so that I can reuse the variable without having to resolve it multiple times. However, I am encountering an issue where ...

ngModel is not taken into account when processing form data

Attempting to make use of a dynamic form in AngularJS, the code snippet below has been utilized: <dynamic-form template="formTemplate" ng-model="formData" ng-submit="processForm()"> </dynamic-form> The controller script inc ...

Create an interactive webpage that automatically generates new HTML elements after retrieving JSON data from a Web API during page load

I am currently in the process of developing a hybrid Android App using Phonegap/Apache Cordova. The main function of my app is to retrieve data from my web API, which is being served through JSON. I have implemented the following code snippet for this task ...

Instructions on how to dynamically update a form field based on the input in another field using conditional statements

I'm seeking advice on how to automatically update a field based on user input without the need for manual saving. For example, if the user types '95' in the input field, the equivalent value displayed should be '1.0' in real-time. ...

The combination of Array.pop and Array.indexOf is not functioning as expected

I'm having an issue with using Array.pop(Array.indexOf(value)). It seems to always delete the last element in the index, even if the value of that index is not what I intended. Can someone provide some guidance on how to resolve this? CheckBoxHandle ...

The request does not include the cookies

My ReactJS client sends a cookie using this NodeJS code snippet: res.cookie("token", jwtCreation, { maxAge: 24 * 60 * 60 * 1000, // Milliseconds (24 hours) sameSite: 'None', // Cross-site requests allowed for modern browser ...

Determining the state update value in useEffect using dispatch and payload in Redux

Apologies for the confusion in the title. I am currently working with React and Redux-toolkit. I encountered an issue where when referencing the updated value in the useState set function, I ended up getting the value before the update. I understand that ...

pausing a timer using JavaScript or jQuery

My goal is to make my clock stop at zero and then display the results page. Unfortunately, I am currently facing difficulties in achieving this. var clock = { time: 2, timeleft: 0, bigben: null, countDown: function() { clock.time--; $("#timer") ...

Modify the hyperlink address in the body of the webpage using jQuery

I'm searching for a solution to modify the href attribute in the following code: <link rel="stylesheet" type="text/css" href="theme1.css"> For instance, changing it from: <link rel="stylesheet" type="text/css" href="theme1.css"> to: & ...

Using JavaScript to Generate Formatting Tags Based on User Selections from a Multiselect Dropdown

When a user selects formatting options (such as bold, italic, underline) from a multiselect dropdown, I need to generate corresponding formatting tags. For example, if the user selects bold and italic, I should create a tag like <b><i></i&g ...

Reduce the number of divs on a webpage while incorporating animated transitions

I've been attempting to incorporate an animation on the width property for my div .panels. I've tried using transition-property in CSS and also with .animate() in jQuery, but unfortunately, it doesn't seem to be working. I also noticed that ...

Implementing a dynamic update of an HTML element's content with JSON data - Learn how!

My task involves creating a quiz application where I need to show the answers along with images of the choices stored in my JSON data. However, I encounter an error: Uncaught TypeError: Cannot set properties of null (setting 'src') when I attempt ...

Retrieve the stylesheet based on the presence of a specific class

Is there a way to dynamically add a CSS stylesheet based on the presence of a specific class on a page? For example, instead of checking the time and loading different stylesheets, I want to load different stylesheets depending on the class present in the ...

Efficiently loading components in Angular using browserify with lazy loading

As I work on developing the architecture of a complex application with Angular, I have started with the angular-seed project which seems to be a solid starting point. However, one issue that concerns me is how Angular apps tend to load everything upfront b ...

Unable to perform Undo function in monaco editor

Currently in my Angular 7 project, I have integrated the Monaco editor for coding purposes. One issue I am facing is that when I make a change to the code and then press ctrl+z to undo it, the previous code is successfully restored. However, if I change th ...

Utilizing Angular 2 for a dynamic Google Map experience with numerous markers

I am currently working on an Angular2 project that involves integrating Google Maps. My goal is to display multiple markers around a specific area on the map. Although I have been able to get the map running, I am facing issues with displaying the markers ...

Is there a way to transfer PHP user information (session) to a Node.js server?

Currently, I have two separate pages set up for my website. One is the Chat page, which operates on Node.js and Socket.io using WebSockets. The other is the main page where users login and access various features. I am looking to implement a system where, ...

Custom sparkline array

In working on my sparkline chart, I have the following code snippet: <div sparkline="" values="4,4,7,5,9,6,4" data-type="line" data-height="80" data-width="100%" data-line-width="2" data-line-color="#dddddd" data-spot-color="#bbbbbb" data-fill-c ...

Ensure that only a single onmouseover event is triggered when hovering over multiple elements

I want to create a simple code snippet like the one below: <span onmouseover="alert('hi')">Hello, <span onmouseover="alert('hello')">this</span> is a test</span> However, I need to ensure that when hovering ove ...