javascript - Strange behavior when deep cloning dynamic objects - Stack Overflow

admin2025-01-07  4

I have been trying to deep clone a dynamic object in typescript and all methods just return an empty {} object for this specific scenario!!

Here is the type of object I am trying to deep clone

fullValues: { [key : string ] : Array<string> },

NOTE: fullValues is passed to a react component and the below mentioned operations happen in this react component! fullValues is NEVER directly mutated throughout the lifecycle of the program and it is initially a state in the parent component as shown below:

const facetValues: { [key: string ] : Array<string> } = {};

// Type => facetedData?: FacetCollectionType
if (facetedData) {
    Object.entries(facetedData).forEach(([key, value]) => {
        Object.defineProperty(facetValues, key, { value: [] as string[]});
    });
}

const [ facets, setFacets ] = useState<{ [key: string ] : Array<string> }>(facetValues);


{facetedData && 
                            
    Object.keys(facetedData).length !== 0 ?
        Object.entries(facetedData).map(([key, options]) => (
            <DataTableFacetedFilter
                key={key}
                options={options}
                mainKey={key}
                title={key}
                fullValues={facets}
                setSelectedValues={setFacets}
            />
        ))
        :
        null
}

Random example of how this object can be structured:

{
   status: [],
   plan: [],
}

I tried the following methods for deepcloning:

Using lodash deepclone

console.log(fullValues); // outputs { status: [], plan: [] }
console.log("after deep clone => ");
console.log(_cloneDeep(fullValues)); // outputs {}

Using JSON stringify method

console.log(fullValues); // outputs { status: [], plan: [] }
console.log("after deep clone => ");
console.log(JSON.parse(JSON.stringify(fullValues))); // outputs {}

However if I do this

let fullValues: { [key : string ] : Array<string> }  = { status: [], plan: [] };
console.log(fullValues); // outputs { status: [], plan: [] }
console.log("after deep clone => ");
console.log(_cloneDeep(fullValues)); // outputs { status: [], plan: [] }

It works here.

There seems to be no logic to why this is happening? It makes no sense!

I have been trying to deep clone a dynamic object in typescript and all methods just return an empty {} object for this specific scenario!!

Here is the type of object I am trying to deep clone

fullValues: { [key : string ] : Array<string> },

NOTE: fullValues is passed to a react component and the below mentioned operations happen in this react component! fullValues is NEVER directly mutated throughout the lifecycle of the program and it is initially a state in the parent component as shown below:

const facetValues: { [key: string ] : Array<string> } = {};

// Type => facetedData?: FacetCollectionType
if (facetedData) {
    Object.entries(facetedData).forEach(([key, value]) => {
        Object.defineProperty(facetValues, key, { value: [] as string[]});
    });
}

const [ facets, setFacets ] = useState<{ [key: string ] : Array<string> }>(facetValues);


{facetedData && 
                            
    Object.keys(facetedData).length !== 0 ?
        Object.entries(facetedData).map(([key, options]) => (
            <DataTableFacetedFilter
                key={key}
                options={options}
                mainKey={key}
                title={key}
                fullValues={facets}
                setSelectedValues={setFacets}
            />
        ))
        :
        null
}

Random example of how this object can be structured:

{
   status: [],
   plan: [],
}

I tried the following methods for deepcloning:

Using lodash deepclone

console.log(fullValues); // outputs { status: [], plan: [] }
console.log("after deep clone => ");
console.log(_cloneDeep(fullValues)); // outputs {}

Using JSON stringify method

console.log(fullValues); // outputs { status: [], plan: [] }
console.log("after deep clone => ");
console.log(JSON.parse(JSON.stringify(fullValues))); // outputs {}

However if I do this

let fullValues: { [key : string ] : Array<string> }  = { status: [], plan: [] };
console.log(fullValues); // outputs { status: [], plan: [] }
console.log("after deep clone => ");
console.log(_cloneDeep(fullValues)); // outputs { status: [], plan: [] }

It works here.

There seems to be no logic to why this is happening? It makes no sense!

Share Improve this question edited 2 days ago mani-hash asked 2 days ago mani-hashmani-hash 7747 silver badges12 bronze badges 2
  • 1 Why close it? There's absolutely enough information here to answer the question. – Tim Moore Commented 2 days ago
  • In fact, I think it’s way more than necessary: it ought to be a minimal complete, reproducible example. Also, it better have a less vague question title before it is reopened. – dumbass Commented 2 days ago
Add a comment  | 

2 Answers 2

Reset to default 1

This stems from the use of Object.defineProperty to set the fields.

You can reduce it to this simplified example.

const values : { [key: string]: Array<string> } = {};
console.log(`1: ${JSON.stringify(values)}`); // 1: {}
Object.defineProperty(values, 'status', { value: 'ok' });
console.log(`2: ${JSON.stringify(values.status)}`); // 2: "ok"
console.log(`3: ${JSON.stringify(values)}`); // 3: {}

According to the MDN documentation for defineProperty:

By default, properties added using Object.defineProperty() are not writable, not enumerable, and not configurable.

By being non-enumerable, these methods for cloning the object do not see these properties. Assuming that defineProperty was used deliberately to make the properties non-writable, you can make them explicitly enumerable:

const values : { [key: string]: Array<string> } = {};
console.log(`1: ${JSON.stringify(values)}`); // 1: {}
Object.defineProperty(values, 'status', { value: 'ok', enumerable: true });
console.log(`2: ${JSON.stringify(values.status)}`); // 2: "ok"
console.log(`3: ${JSON.stringify(values)}`); // 3: {"status":"ok"}

If making the properties non-writable and non-configurable isn't required, a simpler solution is to use an indexed assignment:

const values : { [key: string]: Array<string> } = {};
console.log(`1: ${JSON.stringify(values)}`); // 1: {}
const key = 'status';
values[key] = 'ok'; // `values.status = 'ok'` would also work for non-variable keys
console.log(`2: ${JSON.stringify(values.status)}`); // 2: "ok"
console.log(`3: ${JSON.stringify(values)}`); // 3: {"status":"ok"}

Between the the snippet that works and your lodash snippet, the only difference is that you're explicitly assigning a value to fullValues immediately before trying to deep clone it. That leads me to believe that fullValues is just empty (can you double-check that the console log for lodash and JSON.stringify snippets do actually print the object out with each render?).

If the parent component is supposed to reset and re-populatefacetValues with each render, then this might be an issue of a race/async condition in state setting during the parent's lifecycle: Parent loads -> facetValues = {} -> facets == facetValues -> facetedData may or may not be updated -> if facetedData is defined, facetValues gets updated BUT there is no listener to update facets after it was set to {}. Also, note that facetedData is an object, so the if block will be entered even if it was set to {}.

Instead of the current parent logic, try using useEffect to listen to changes in facetedData and call a callback (with a facetedData dependency) to set the state of facets, e.g.

const [ facets, setFacets ] = useState<{ [key: string ] : Array<string> | null }>(null);

const mapFacets = useCallback(() => {
    if (!facetedData) return;

    const facetValues: { [key: string ] : Array<string> } = {};

    Object.entries(facetedData).forEach(([key, value]) => {
        facetValues[key] = value as string[]
    });
 
    setFacets(facetValues)
}, [facetedData]);

useEffect(() => {
    mapFacets();
}, [facetedData]);

{facets ?
                        
Object.keys(facetedData).length !== 0 ?
    Object.entries(facetedData).map(([key, options]) => (
        <DataTableFacetedFilter
            key={key}
            options={options}
            mainKey={key}
            title={key}
            fullValues={facets}
            setSelectedValues={setFacets}
        />
    ))
    :
    null
}

Edit: good catch by Tim. I updated the code.

转载请注明原文地址:http://conceptsofalgorithm.com/Algorithm/1736265587a1056.html

最新回复(0)