javascriptreactjsreduxredux-sagaself-destruction

How to update a value in destructing and loop


What I try to achieve:

I want to update a value in an obj, which is part of the element of array. See the code below will give you better idea.

There is an issue that I update the value of object, via reference, instead of making a copy. This causes the state behave strangely.

I try to change it to making a copy, but I am not sure.

e.g.

const returnObj = {
  ...objs,
  fields: [{name, value}, {name, value}, {name, value_update_this_only}, ...],
};
// This is the current code
export function* onChange(action) {
  // get partial state from redux state
  const list = yield select((state) => state.list);
  let objs = list[action.index];

  // * e.g. objs.fields === [{name, value}, {name, value}, ...]
  // * basically following, find the correct field and update its value
  // * following has problem, beause we change the value of a reference,
  // * instead we should make a new copy, so redux can react
  objs.fields.map((field) => {
    if (field.name === action.fieldName) {
      field["value"] = action.fieldValue;
    }
    return field;
  });

  // fire to redux reducer
  yield put({
    type: "UPDATE",
    prop: obj,
    docIndex: action.index,
  });
}

// the problem: I don't know how to do it in destructing manner.
const returnObj = {
  ...objs,
  fields: [],
};


Solution

  • I think rather than try and come up with a single destructuring statement that makes this work, it's easier to digest (and arguably more readable) in smaller steps:

    1. Make a shallow copy of objs; call it copy for now
    2. Recreate fields array and every item within it
    3. For the desired array item, update its value
    4. Set the copy.fields to the array created in 2
    // Step 1: Shallow copy
    let copy = { ...objs }
    
    // Step 2: Recreate fields and every item
    let fields = copy.fields.map((field) => ({
      ...field
    }))
    
    // Step 3: Update value of desired item
    fields.forEach((field) => {
      if (field.name === action.fieldName)
        field.value = action.fieldValue
    })
    
    // Step 4: Reassign fields to the copy
    copy.fields = fields
    

    Refactoring this, steps 2-4 can be combined into one step without sacrificing that much readability:

    let copy = { ...objs }
    
    copy.fields = copy.fields.map((field) => ({
      ...field,
      value: field.name === action.fieldName ? action.fieldValue : field.value,
    }))
    

    It's been a long time since I've used redux or sagas, so I'm not sure whether fields needs to be an entirely new array or if just the changed object within fields needs to be new, but the above can be modified to accommodate either need.