Search code examples
arraysreactjspath-traversal

Traverse an object of arrays, for a nested form


I have a dynamic form, that is nested, the form is to describe an overhead gantry crane.

so the structure looks like this:

let equipmentInfo = {   
  bridges:[{
    trolleys:[{
      hoists:[{
      
      }]
    }]
  }] 
}

I have a function component for each bridge, trolley, and hoist set of fields. I pass down an array that describes its location in the tree like:

let keyPath = [['arrayLocation', indexOfEntry]] // [['bridge', 1], ['trolley',3], ['hoist', 0]]

My question surrounds the use of this getObject function

const getObject = (object, location) => {
    if(object == undefined) return {}
    if(location.length == 0) return object
    if(location.length == 1) return object[location[0][0]][location[0][1]]
    if(location.length == 2) return object[location[0][0]][location[0][1]][location[1][0]][location[1][1]]
    if(location.length == 3) return object[location[0][0]][location[0][1]][location[1][0]][location[1][1]][location[2][0]][location[2][1]] 
    if(location.length == 4) return object[location[0][0]][location[0][1]][location[1][0]][location[1][1]][location[2][0]][location[2][1]][location[3][0]][location[3][1]] 
    if(location.length == 5) return object[location[0][0]][location[0][1]][location[1][0]][location[1][1]][location[2][0]][location[2][1]][location[3][0]][location[3][1]][location[4][0]][location[4][1]] 
    if(location.length == 6) return object[location[0][0]][location[0][1]][location[1][0]][location[1][1]][location[2][0]][location[2][1]][location[3][0]][location[3][1]][location[4][0]][location[4][1]][location[5][0]][location[5][1]]
    if(location.length == 7) return object[location[0][0]][location[0][1]][location[1][0]][location[1][1]][location[2][0]][location[2][1]][location[3][0]][location[3][1]][location[4][0]][location[4][1]][location[5][0]][location[5][1]][location[6][0]][location[6][1]]
}

its used in the handleOnChange like this:

const handleInputChange = (e, keyPath) => {
    
  const { name, value } = e.target;
  const object = {...equipmentInfo}

  const targetObject = getObject(object, keyPath)
  targetObject[name] = value

  setObject(targetObject, object, keyPath)
    
  setEquipmentInfo(object);
};

Is there a better way to do this? not only for better readability, but to support n amount of branches.

  <BridgeFields 
    keyPath={newKeypath} 
    handleOnChange={handleOnChange}
    equipmentInfo={EquipmentInfo}
    setEquipmentInfo={setEquipentInfo}
    
  />

Here is the implementation example:

// inside <BridgeFields/>
getObject(equipmentInfo, keyPath).trolleys.map((trolly, index)) => {

  // add to the keyPath the compenent and its index
  let newKeyPath = [...keyPath, ["trolleys", index]]
  
  // Form fields describing a bridge 
  <Form.Component 
    type='text' 
    name='serialNumber'
    onChange={(e)=>{handleOnChange(e, newKeyPath)}
  />

  // also include its child component fields (hoists, etc) .. like:
  <HoistFields 
    keyPath={newKeypath} 
    handleOnChange={handleOnChange}
    equipmentInfo={EquipmentInfo}
    setEquipmentInfo={setEquipentInfo}
}

This works and its actually quite fast, but im looking to improve what I've done here. there is more to this equipmentInfo object and i would like to make it better.

I appreciate any advice thanks!


Solution

  • One possible solution (with the assumption that the location array will have the exact keys namely 'bridges', 'trolleys', 'hoists') is to use Array reduce like so:

    const getObj = (obj = equipmentInfo, location = [['bridges', 1], ['trolleys', 0], ['hoists', 0]]) => (
        location.reduce(
            (acc, itm) => ({ ... acc[itm[0]][itm[1]] }), obj || {}
      )
    );
    

    Explanation

    1. For each item in location array, get the prop (indicated by the 0-th index).
    2. The value corresponding to the prop is an array.
    3. Use location element's value at index-1 to identify the required array element (of the array at step-2 above)

    Apologies, if this explanation is not too clear.

    Code-snippet to verify

    const equipmentInfo = {
      bridges: [{
        trolleys: [{
          hoists: [{
            k0: 'b0t0h0v0'
          }, {
            k1: 'v1'
          }, {
            k2: 'v2'
          }]
        }, {
          hoists: [{
            k0: 'b0t0h1v0'
          }, {
            k1: 'v1'
          }, {
            k2: 'v2'
          }]
        }, {
          hoists: [{
            k0: 'b0t0h2v0'
          }, {
            k1: 'v1'
          }, {
            k2: 'v2'
          }]
        }, {
          hoists: [{
            k0: 'b0t1h0v0'
          }, {
            k1: 'v1'
          }, {
            k2: 'v2'
          }]
        }, {
          hoists: [{
            k0: 'b0t1h1v0'
          }, {
            k1: 'v1'
          }, {
            k2: 'v2'
          }]
        }, {
          hoists: [{
            k0: 'b0t1h2v0'
          }, {
            k1: 'v1'
          }, {
            k2: 'v2'
          }]
        }]
      }, {
        trolleys: [{
          hoists: [{
            k0: 'b1t0h0v0'
          }, {
            k1: 'v1'
          }, {
            k2: 'v2'
          }]
        }, {
          hoists: [{
            k0: 'b1t0h1v0'
          }, {
            k1: 'v1'
          }, {
            k2: 'v2'
          }]
        }, {
          hoists: [{
            k0: 'b1t0h2v0'
          }, {
            k1: 'v1'
          }, {
            k2: 'v2'
          }]
        }, {
          hoists: [{
            k0: 'b1t1h0v0'
          }, {
            k1: 'v1'
          }, {
            k2: 'v2'
          }]
        }, {
          hoists: [{
            k0: 'b1t1h1v0'
          }, {
            k1: 'v1'
          }, {
            k2: 'v2'
          }]
        }, {
          hoists: [{
            k0: 'b1t1h2v0'
          }, {
            k1: 'v1'
          }, {
            k2: 'v2'
          }]
        }]
      }]
    };
    
    const getObj = (obj = equipmentInfo, location = [
      ['bridges', 1],
      ['trolleys', 0],
      ['hoists', 0]
    ]) => (
      location.reduce(
        (acc, itm) => ({ ...acc[itm[0]][itm[1]]
        }), obj || {}
      )
    );
    
    
    console.log('b 1, t 0, h 0', getObj());
    console.log('b 1, t 1, h 0', getObj(equipmentInfo, [
      ['bridges', 1],
      ['trolleys', 1],
      ['hoists', 0]
    ]));
    console.log('b 0, t 1, h 0', getObj(equipmentInfo, [
      ['bridges', 0],
      ['trolleys', 1],
      ['hoists', 0]
    ]));
    console.log('b 0, t 0, h 0', getObj(equipmentInfo, [
      ['bridges', 0],
      ['trolleys', 0],
      ['hoists', 0]
    ]));

    Edit

    Set-object code:

    const setObj = (updatedObj, obj = equipmentInfo, location = [
      ['bridges', 1],
      ['trolleys', 0],
      ['hoists', 1]
    ]) => {
      location.reduce((acc, itm, idx) => {
        const tgtObj = acc[itm[0]][itm[1]];
        if (idx < location.length - 1) return tgtObj;
        acc[itm[0]][itm[1]] = {
          ...tgtObj,
          ...updatedObj
        };
        return null;
      }, obj);
    };
    
    let myObj = {
      ...equipmentInfo
    };
    setObj({
      zztest: 'okay'
    }, myObj);
    console.log(myObj);