辅助函数在反应中覆盖映射列表中的先前值

helper function overwriting previous values in mapped list in react

我有一个申请人的映射列表,每个都包含一个 finalStatus 属性。此映射列表呈现带有自定义下拉列表的组件,并将申请人作为道具。

我有一个包含下拉对象的数据文件,我将其拉入组件以呈现下拉列表。

其中每一个都有一个禁用的 属性 true 或 false,最初设置为 false,就像这样...

    complianceStatus: [
        {id: 1,  value: 'None', disabled: false },
        {id: 2,  value: 'Provisionally', disabled: false},
        {id: 3,  value: 'Fully', disabled: false },
    ],

然后当组件挂载时 运行 一个 useEffect 来获取下拉数据并对其执行辅助功能,以便根据申请人的值确定禁用应该是真还是假目的。然后我将此对象设置为 state 以呈现下拉列表。

这是使用效果

    useEffect(() => {
        setDropdownData(utils.getDropdownData('complianceStatus', applicant));
    }, [applicant]);

我的 util 函数在这里供参考

export const getDropdownData = (type, applicant) => {
            const optionData = mergeArray(data.dropdownData[type], type, applicant);
        return optionData;
    }


const mergeArray = (dropDownData, type, data) => {
    if (type === 'actionList') {
        mergeArrayActionListHelper(dropDownData, data);
    }
    if (type === 'complianceStatus') {
        disableComplianceStatus(dropDownData, data);
    }

    return dropDownData;
}


export const disableComplianceStatus = (dropdownData, applicant) => {
   
    switch(applicant.finalStatus) {
        case 'fully_compliant':
            dropdownData[0].disabled = true;
            dropdownData[1].disabled = true;
            dropdownData[2].disabled = true;
            return;
        case 'partially_compliant':
            dropdownData[0].disabled = true;
            dropdownData[1].disabled = true;
            dropdownData[2].disabled = false;
            return;
        default:
            dropdownData[0].disabled = true;
            dropdownData[1].disabled = false;
            dropdownData[2].disabled = false;
            return;


    }

}

我的下拉列表正在呈现,但有一个奇怪的怪癖。

问题是,列表中的最后一位申请人似乎覆盖了之前所有申请人的残疾属性。

知道为什么会这样吗?

编辑有关申请人的更多信息

申请者是一个大物件几个组件起来。它适用于 table 风格的仪表板,每个申请人都有一行。

它首先传递给未设置状态的 DashboardRow 组件。

return (
        <div className='dashboard-row'>
            {
                staticDash ? (
                    <DashboardRowStatic applicant={applicant}/>
                ) : (
                    <DashboardRowDynamic 
                        applicant={applicant} 
                      
                    />
                )

            }
        </div>
    )

这会在行上呈现实际项目,组件看起来像这样

const DashboardRowDynamic = ({applicant, applicantNumber, applicantLength}) => {
    const dynamicTableData = useSelector(state => state.dashboard.dynamicTableData);

    return (
        dynamicTableData && dynamicTableData.length > 0 && 
        <div className='dashboard-row-dynamic'>
            {
                dynamicTableData[1].values.map(value => {
                    if (value === 'finalStatus') {
                        return (
                            <div key={value} className='col-md center'>
                                <ProfileFinalStatus 
                                    applicantFromDash={applicant} 
                                    applicantNumber={applicantNumber} 
                                    applicantLength={applicantLength} 
                                />
                            </div>
                        )
                    } 
         
                    else if (value === 'suspendedMessage') {
                        return (
                            <div key={value} className='col-md'>
                                <TooltipLg title={applicant[value]}>
                                    <p className='orka-semi-p tooltip'>{applicant[value]}</p>
                                </TooltipLg>
                            </div>
                        )
                        
                    } else {
                        return (
                            <div key={value} className='col-md'>
                                <p className='orka-semi-p'>{applicant[value]}</p>
                            </div>
                        )
                    } 
                })
            }
        </div>
    )
}

然后这是整个 FinalStatus 组件供参考

const ProfileFinalStatus = ({applicantFromDash, applicantNumber = 0, applicantLength = 20}) => {

    const applicant = useSelector(state => state.profile.applicant);
    const dispatch = useDispatch();
    const [dropdownData, setDropdownData] = useState([]);
    const [defaultData, setDefaultData] = useState(null);
    const [localApplicant, setLocalApplicant] = useState(null);

   
    useEffect(() => {
        const applicantSelect = applicantFromDash ? applicantFromDash : applicant;
        setLocalApplicant(applicantSelect);
        setDropdownData(utils.getDropdownData('complianceStatus', applicantSelect));
    }, [applicant, applicantFromDash]);

    useEffect(() => {
        if (localApplicant) {
            const data = dropdownData.filter(item => {
                return item.dbValue === localApplicant.finalStatus;
            });

            getIcon(data);           
            setDefaultData(data[0]);
        }
    // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [JSON.stringify(dropdownData), JSON.stringify(localApplicant)]);



    const getIcon = (data) => {
        switch(data[0].dbValue) {
            case 'partially_compliant':
                data[0].icon = partially;
                return;
            case 'fully_compliant':
                data[0].icon = fully;
                return;
            default:
                data[0].icon = none;
                return;
        }
    }


    const getDropdownVal = (val) => {
        const payload = {
            finalStatus: val.dbValue
        }

        apiApplicant.updateApplicant(localApplicant.workerUuid, payload)
        .then(res => {
            const data = dropdownData.filter(item => {
                return item.dbValue === payload.finalStatus;
            });
            localApplicant.finalStatus = val.dbValue;
            setDropdownData(utils.getDropdownData('complianceStatus', localApplicant));
           
            setDefaultData(data[0]);
            if (applicantFromDash) {
                dispatch(refetchApplicants());
            }
            dispatch(setAlert({
                type: 'success',
                message: 'Successfully updated compliance status',
                isVisible: true
            }));
        })
        .catch(err => {
            dispatch(setAlert({
                type: 'error',
                message: 'Error updating face to face status',
                isVisible: true
            }));
        })

    }

    const getPosition = () => {
        let position;
        // applicantLength = 2;
        // if (applicantLength === 2) {
        //     position = 'top-center';
        // } else {
            position = applicantNumber > 16 ? 'top-center' : 'bottom-center';
        // }
        // console.log(applicantLength)
        // console.log(position)

        return position;
    }
    return (
        <div>
            {
                defaultData &&
                (
                    <Dropdown 
                        type="complianceStatus" 
                        dropdownData={dropdownData}
                        defaultData={defaultData} 
                        getDropdownVal={getDropdownVal} 
                        width="160"
                        height='24'
                        mobileResponsive={false}
                        position={getPosition()}

                    />
                )
            }
            
            
        </div>
    )
}

export default ProfileFinalStatus;

编辑 2 dropdownData 在文件 data.js

export const dropdownData = {
    searchBy: [
        {id: 1,  value: 'First Name', dbValue: 'name', class: "dropdown-grey"},
        {id: 2,  value: 'Surname', dbValue: 'surname', class: "dropdown-grey"},
        {id: 3,  value: 'Email', dbValue: 'email', class: "dropdown-grey"},
        {id: 4,  value: 'NI Number', dbValue: 'nationalInsuranceNumber', class: "dropdown-grey"}
    ],
    sortBy: [
        {id: 1,  value: 'Urgent', dbValue: 'urgent', sortAsc: false, class: "dropdown-grey"},
        {id: 2,  value: 'Name', dbValue: 'name', sortAsc: false, class: "dropdown-grey"},
        {id: 3,  value: 'Email', dbValue: 'email', sortAsc: false, class: "dropdown-grey"},
        {id: 4,  value: 'Created Date', dbValue: 'created_at', sortAsc: false, class: "dropdown-grey"}
    ],
    submissionStatus: [
        {id: 1,  value: 'Pending', dbValue: 'pending', class: "dropdown-grey"},
        {id: 2,  value: 'Accepted', dbValue: 'accepted', class: "dropdown-grey"},
        {id: 3,  value: 'Rejected', dbValue: 'rejected', class: "dropdown-grey"},
        {id: 4,  value: 'Unsuccessful', dbValue: 'unsuccessful', class: "dropdown-grey"}
    ],
    complianceStatus: [
        {id: 1,  value: 'None', dbValue: 'not_compliant', class: "dropdown-grey", disabled: false },
        {id: 2,  value: 'Provisionally', dbValue: 'partially_compliant', class: "dropdown-yellow", disabled: false},
        {id: 3,  value: 'Fully', dbValue: 'fully_compliant', class: "dropdown-green", disabled: false },
    ],
    actionList: [
        {id: 1,  value: 'Suspended', openModal: true, isSuspended: false },
        {id: 2,  value: 'Mark Urgent', openModal: false, isUrgent: false },
        {id: 3,  value: 'Edit User', openModal: true },
        {id: 4,  value: 'CV Online', openModal: false },
        {id: 5,  value: 'Identity Check', openModal: true },
    ],
    employmentTypes: [
        {id: 1,  dbValue: 'employed', value: 'Employed', fullView: true, class: 'blue-outline' },
        {id: 2,  dbValue: 'self-employed', value: 'Self Employed', fullView: false, class: 'blue-outline'   },
        {id: 3,  dbValue: 'studying', value: 'Studying', fullView: true, class: 'blue-outline'   },
        {id: 4,  dbValue: 'carer', value: 'Carer', fullView: false, class: 'blue-outline'  },
        {id: 5,  dbValue: 'redundant', value: 'Redundant', fullView: true, class: 'blue-outline'  },
        {id: 6,  dbValue: 'sick', value: 'Sick', fullView: false, class: 'blue-outline'  },
        {id: 7,  dbValue: 'travelling', value: 'Travelling', fullView: false, class: 'blue-outline'  },
        {id: 8,  dbValue: 'volunteering', value: 'Volunteering', fullView: true, class: 'blue-outline'  },
        {id: 9,  dbValue: 'unemployed-claiming', value: 'Unemployed Claiming', fullView: false, class: 'blue-outline'  },
        {id: 10,  dbValue: 'unemployed-not-claiming', value: 'Unemployed Not Claiming', fullView: false, class: 'blue-outline'  },
    ],
    faceToFaceActions: [
        {id: 1, value: 'Pending', dbValue: 'pending', class: "dropdown-grey"},
        {id: 2, value: 'Approve', dbValue: 'passed', class: "dropdown-grey"},
        {id: 3, value: 'Reject', dbValue: 'failed', class: "dropdown-grey"}
    ]
}

然后有一个导入数据的 utils 函数文件

import * as data from './data.js';

可以看到上面的utils函数

然后在最终状态组件 utils 是这样导入的

import * as utils from '../../utils/utilsFunctions';

然后在 ProfileFinalStatus 组件中调用它,如上面的代码片段所示。

问题

我认为问题是 disableComplianceStatus 实用程序中的 dropdownData 对象突变。

我认为发生的步骤是:

  1. DashboardRowDynamic 呈现 table 数据数组并呈现 ProfileFinalStatus 组件。
  2. ProfileFinalStatus 通过调用 setDropdownData(utils.getDropdownData('complianceStatus', applicantSelect));.
  3. 填充其 dropdownData 状态数组
  4. getDropdownDatadata.dropdownData[type] 作为 dropdownData 传递给 mergeArray 和 returns mergeArray 的 return 值。
  5. mergeArray 调用 disableComplianceStatus 并传递 dropdownData 和 returns dropdownData.
  6. disableComplianceStatus 直接改变 dropdownData 对象,设置 disabled 属性.

当这发生在循环中时,每次迭代都会改变同一个对象,因为它仍然是对存储在从 data.js 文件导出的对象中的原始对象的引用。每个迭代项都会改变引用的对象,并且正在改变所有先前元素具有的对象引用。

解决方案

在此流程中的某个时刻,您只需要浅拷贝 dropdownData 对象,以便每个映射元素都获得自己的副本进行变异。这个 IMO 的一个好地方是 disableComplianceStatus 实用程序。与其改变传递的对象,不如复制、设置属性,然后 return 新对象。 mergeArray 应该 return that 新对象而不是传递的 dropdownData 对象。

我向您提交以下更改

  1. mergeArray return它调用的实用程序的结果。

    const mergeArray = (dropDownData, type, data) => {
      if (type === 'actionList') {
        return mergeArrayActionListHelper(dropDownData, data);
      }
      if (type === 'complianceStatus') {
        return disableComplianceStatus(dropDownData, data);
      }
    
      return dropDownData;
    }
    
  2. disableComplianceStatus 浅拷贝 dropdownData 对象 然后 更新属性,returning 新对象引用。这些需要在您更新其属性的每个级别完成,因此这也包括数组元素。

注意:您需要对 mergeArrayActionListHelper 实用程序应用类似的修复程序。

    export const disableComplianceStatus = (dropdownData, applicant) => {
      let disabled = [];

      switch(applicant.finalStatus) {
        case 'fully_compliant':
          disabled = [true, true, true];
          break;
        case 'partially_compliant':
          disabled = [true, true, false];
          break;
        default:
          disabled = [true, false, false];
          break;
      }

      // return new mapped array with copies of all elements.
      return dropdownData.map((el, i) => ({
        ...el,
        disabled: !!disabled[i],
      }));
    }