简体   繁体   中英

props being overwritten by state change?

I've come across a strange bug in my code and I'm struggling to understand the cause.

In a nutshell, it appears that the props I'm passing to a functional component are being overwritten by a state change (which is most definitely not being propagated up to the parent). I'm guessing this is because I'm doing something wrong when creating new objects which are then used to update the state, but I'm struggling to see where.

I'm using react-select, and it's the change function on this that's triggering the bug. I don't think it's that in itself which is the problem, as I've used it elsewhere in the project and not come across this error.

Here's a cut-down version of my code (imports removed):

interface IPropertyValue {
    field1: number;
    field2: number;
    field3: number;
    field4: string;        
}

interface INestedPropertyType {
    systemProperty?: IPropertyValue;
}

interface IPropertyType {
    current: INestedPropertyType;
    desired: INestedPropertyType;
}

interface IComponentProps {
    currentProperty: IPropertyType;
}

export function Component(passedProps: IDynamicPropsType) {
    const props = passedProps as IComponentProps;
    const [propName, setPropName] = useState(props.currentProperty);

    const editValue = (typeOfProperty: 'current' | 'desired', newValue: string) => {

        let existingPropName = {...propName} as IPropertyType; // create a new object from existing one
        let alteredPropName = {} as ISystemProperty; // we reassign this later on based on typeOfProperty

        if(typeOfProperty === 'current') {
            if(existingPropName.current.systemProperty) {
                alteredPropname = {...existingPropName.current.systemProperty} as IPropertyValue;
            } else {
                existingPropName.current.systemProperty= {} as IPropertyValue;
                alteredPropName= {} as IPropertyValue;
            }
        } else if (typeOfProperty=== 'desired') {
            if(existingPropName.desired.systemProperty) { // could be undefined...
                // is defined, create a new object for it using existing values
                existingPropName= {...existingPropName.desired.systemProperty} as IPropertyValue;
            } else {
                // not defined, create empty object for it
                existingLineage.desired.systemProperty= {} as IPropertyValue;
                alteredPropName= {} as IPropertyValue;
            }
        }

        const newValues = newValue.split('|'); // this comes from the select option; it's a string separated by '|' symbols. It will always be 4 values long.
        if(newValues.length === 4) {
            alteredPropName = {
                ...alteredPropName,
                field1: parseInt(newValues[0], 10),
                field2: parseInt(newValues[1], 10),
                field3: parseInt(newValues[2], 10),
                field4: newValues[3]
            }
        }

        // update the relevant property with the updated object
        if(typeOfProperty === 'current') {
            existingPropName.current.systemProperty = alteredPropName;
        } else if(typeOfProperty=== 'desired') {
            existingPropName.desired.systemProperty = alteredPropName;
        }

        // update the state
        setPropName(existingLineage);
    }

    // here's the weird part. 'props' is changed after the editValue function is called, even though it should be passed in from the parent component and left unchanged (apart from casting it to IComponentProps)
    console.log(props);

    return (<>
        <Select
            options={<options here>}
            onChange={(selectedOption: ValueType<any>) => {
                editValue('current', selectedOption.value);
            }}
        />
        <Select
            options={<options here>}
            onChange={(selectedOption: ValueType<any>) => {
                editValue('desired', selectedOption.value);
            }}
        />
    </>);
}

There are some odd parts in the code, such as casting passedProps to props , but they're there for a reason - the parent component is a wrapper that's shared between multiple other components.

Yes, you are right. Your props are being overwritten because you are passing them directly in

useState(props.currentProperty)

In Javascript Objects are a reference type. Calling editValue will result in updating the nested objects.

You can create a deep copy of the props before you pass it to useState . Simple approach would be to use JSON.stringify and JSON.parse

const [propName, setPropName] = useState(JSON.parse(JSON.stringify(props.currentProperty)));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM