Search code examples
javascriptarraysjavascript-objects

Loop through an array of objects to update object values and append additional objects


I have a fields array with objects and I'm trying to loop this array:

fields: [
    {
        name: "seasonId",
        type: "select",
        label: "Season ID",
        placeholder: "Select a season id...",
        icon: "id",
        value: "",
        errors: "",
        required: true,
        disabled: true,
        selectOptions: [],
    },
    {
        name: "callTime",
        type: "time",
        label: "Scheduling Call Times",
        placeholder: "Select a time...",
        value: "",
        errors: "",
        required: true,
        disabled: true,
    },
];

To update its values AND append additional objects:

Incoming values:

"fieldValues": {
  "callTimes": [
    "5:45 pm",
    "6:15 pm",
    "6:30 pm",
    "7:00 pm"
  ],
  "selectedSeasonId": "20192020",
  "seasonIds": [
    "20192020",
    "20202021",
    "20212022",
  ]
}

Update field function:

const updateField = (currentField, fieldValues) => {
  switch(currentField.name) {
    case "seasonId":
      return {
        ...currentField,
        selectOptions: fieldValues.seasonIds,
        value: fieldValues.selectedSeasonId,
        disabled: false
      };
    case "callTime":
      const callTimes = fieldValues.callTimes.map((value, key) => ({
        ...currentField,
        name: key <= 0 ? "callTime" : `callTime-${Date.now()}`,
        label: key <= 0 ? "Scheduling Call Times" : "",
        value,
        required: key <= 0,
        disabled: false,
      }));

      return { 
        ...callTimes 
      };
  }
}

And then invoke the function above like so:

const updatedFields = fields.map(field => updateField(field, event));

However, I'm not getting the desired result.

Actual output:

[
  '0': {
    disabled: false
    errors: ""
    icon: "id"
    label: "Season ID"
    name: "seasonId"
    placeholder: "Select a season id..."
    required: true
    selectOptions: ["20192020", "20202021", "20212022"]
    type: "select"
    value: "20192020"
  },
  '1': {
         '0': {  
           disabled: false
           errors: ""
           label: "Scheduling Call Times"
           name: "callTime"
           placeholder: "Select a call time..."
           required: true
           style: {width: "100%"}
           type: "time"
           value: "5:45 pm"
         },
         '1': {
           disabled: false
           errors: ""
           label: ""
           name: "callTime-1565388886669"
           placeholder: "Select a call time..."
           required: false
           style: {width: "100%"}
           type: "time"
           value: "6:15 pm"
         },
         '3': { ... },
         '4': { ... }
      }
];

Expected output:

[
  '0': {
    disabled: false
    errors: ""
    icon: "id"
    label: "Season ID"
    name: "seasonId"
    placeholder: "Select a season id..."
    required: true
    selectOptions: ["20192020", "20202021", "20212022"]
    type: "select"
    value: "20192020"
  },
  '1': {
    disabled: false
    errors: ""
    label: "Scheduling Call Times"
    name: "callTime"
    placeholder: "Select a call time..."
    required: true
    style: {width: "100%"}
    type: "time"
    value: "5:45 pm"
  },
  '2': {
     disabled: false
     errors: ""
     label: ""
     name: "callTime-1565388886669"
     placeholder: "Select a call time..."
     required: false
     style: {width: "100%"}
     type: "time"
     value: "6:15 pm"
  },
  '3': { ... },
  '4': { ... }
];

Any ideas on how I can update values and append additional objects to my fields array? The callTimes array of string values within the field object is dynamic (can contain 1 string or many), so I can't hard code anything.

const fields = [
    {
        name: "seasonId",
        type: "select",
        label: "Season ID",
        placeholder: "Select a season id...",
        icon: "id",
        value: "",
        errors: "",
        required: true,
        disabled: true,
        selectOptions: [],
    },
    {
        name: "callTime",
        type: "time",
        label: "Scheduling Call Times",
        placeholder: "Select a time...",
        value: "",
        errors: "",
        required: true,
        disabled: true,
    },
];

const fieldValues = {
  "callTimes": [
    "5:45 pm",
    "6:15 pm",
    "6:30 pm",
    "7:00 pm"
  ],
  "selectedSeasonId": "20192020",
  "seasonIds": [
    "20192020",
    "20202021",
    "20212022",
  ]
};


const updateField = (currentField, event) => {
  switch(currentField.name) {
    case "seasonId":
      return {
        ...currentField,
        selectOptions: fieldValues.seasonIds,
        value: fieldValues.selectedSeasonId,
        disabled: false
      };
    case "callTime":
      const callTimes = fieldValues.callTimes.map((value, key) => ({
        ...currentField,
        name: key <= 0 ? "callTime" : `callTime-${Date.now()}`,
        label: key <= 0 ? "Scheduling Call Times" : "",
        value,
        required: key <= 0,
        disabled: false,
      }));

      return { 
        ...callTimes 
      };
  }
};

const updatedFields = fields.map(field => updateField(field, event));

console.log(updatedFields);


Solution

  • Using reduce instead of map, I believe I am getting the right output:

    const updateField = (result, currentField) => {
      switch (currentField.name) {
        case 'seasonId':
          return [
            ...result,
            {
              ...currentField,
              selectOptions: fieldValues.seasonIds,
              value: fieldValues.selectedSeasonId,
              disabled: false
            }
          ]
        case 'callTime':
          const callTimes = fieldValues.callTimes.map(...);
          return [
            ...result,
            ...callTimes
          ]
      }
    }
    const updatedFields = fields.reduce(updateField, [])
    

    Since your callTime case was returning multiple objects in an array, map wouldn't work well in this case as you need to push/add these objects individually to the "final" array, hence this return:

    case 'callTime':
      const callTimes = fieldValues.callTimes.map(...);
      return [
        ...result,
        ...callTimes
      ]
    

    Also, your callTimes came out to be an array, and you tried to spread its items into an object:

    case "callTime":
      const callTimes = fieldValues.callTimes.map(...); // array
      return { 
        ...callTimes 
      };
    

    This is why you were getting an unexpected/weird outcome.

    Here's a demo with the fix:


    Since you asked in the comments how to pass fieldValues to the reducer function since they are being imported from another file, you can do the following:

    const updateField = (result, currentField, fieldValues) => {...}
    
    const updatedFields = fields.reduce(
      (result, field) => updateField(result, field, fieldValues), 
      []
    )
    

    Everything else stays the same.

    Here's another demo: