javascriptreactjsjavascript-objectsshallow-copyspread-syntax

Exploring React Shallow Copy Methods: Spread Operator vs Direct Assignment


I am currently working on a React project and encountered an interesting issue related to shallow copying objects. In my component, I have a form where I collect data, and I noticed that different methods of shallow copying the data object result in different behaviors.

Here are the three approaches I've tried:

Approach 1 (works)

const handleSubmit = async (e) => {
    e.preventDefault();
    actions.updateEmail(...Object.values(localEmailData));
    console.log(store);
};

Approach 2 (works but with console error)

const handleSubmit = async (e) => {
    e.preventDefault();
    actions.updateEmail(...localEmailData);
    console.log(store);
};

The console error reads:

Uncaught (in promise) TypeError: Invalid attempt to spread non-iterable instance.
In order to be iterable, non-array objects must have a [Symbol.iterator]() method.

        

Approach 3 (won't work)

const handleSubmit = async (e) => {
    e.preventDefault();
    actions.updateEmail(localEmailData);
    console.log(store);
};

Can someone explain the differences between these methods, and why Approach 2, although working, triggers a console error? Additionally, why doesn't Approach 3 work?

My React component

    […]
    
    export const Home = () => {
        const { store, actions } = useContext(Context);
    
        const [localEmailData, setLocalEmailData] = useState({
            projectName: store.email.nombreDelProyecto,
            unsubscribeUrl: store.email.unsubscribeUrl,
            header: {
                link1: store.email.header.link1,
                image1: store.email.header.image1,
                alt1: store.email.header.alt1,
                link2: store.email.header.link2,
                image2: store.email.header.image2,
                alt2: store.email.header.alt2,
                link3: store.email.header.link3,
                image3: store.email.header.image3,
                alt3: store.email.header.alt3,
            },
            cta: {
                image: store.email.cta.image,
                link: store.email.cta.link,
            },[…]
});

        const handleInputChange = (e) => {
            setLocalEmailData({ localEmailData, [e.target.name]: e.target.value });
            console.log("localEmailData", localEmailData);
            console.log("Submitting form with data:", localEmailData);
        };
    
        const handleSubmit = async (e) => {
            e.preventDefault();
            actions.updateEmail(...Object.values(localEmailData));
            console.log(store);
        };
    
        return (
            <div className="mt-5">
                <div className="d-flex justify-content-center">
                    {/* Formulario de encabezado */}
                    <Form className="w-50" onSubmit={handleSubmit}>
                        […]
                        <div className="d-flex justify-content-center">
                            <Button type="submit" className="generar-correo">
                                Validate & Generate
                            </Button>
                            <Link to="/pocuromailbuilder" className="generar-correo">
                                <span>Go to </span>&nbsp;<span style={{ fontStyle: 'italic' }}>output</span>
                            </Link>
                        </div>
                    </Form>
                </div>
            </div>
        );
    
    };

Also, my action at flux.js is:

updateEmail: (newData) => {
                setStore({
                    ...getStore(),
                    email: {
                        ...getStore().email,
                        ...newData,
                    }
                });
            },

Any insights or explanations would be greatly appreciated. Thank you!


Solution

  • Okay! Let's start with the analysis (Caution: may have a lot to read)

    Approach 1:

    Consider an object,

    let testObject = {
    key1:value1,
    key2:value2,
    key3:value3
    }
    // When Object.values(testObject) is applied it returns an array with object's 
    // Property Values eg: [value1,value2,value3]
    // and when spread operator is applied, it expands the array and throws the 
    // elements out eg: ...[value1, value2, value3] becomes value1 value2 value3
    

    Three places that accept the spread syntax are Function Arguments, Array and Object Literals. Only iterable values (array or string) can go into the first two places ie., Function Arguments and Array Literals.

    Approach 2:

    Now you know that there are only 3 places we could use spread syntax. Like your second example if I apply,

    console.log(...testObject) // expands the object and each property is printed
    // The catch here is that the console log function takes the expanded values
    // and logs them, returns void and the spread operation expanded the values thinking that the
    // values goes into any of the 3 places mentioned above. **But it did not hence error.**
    

    adding {...testObject}, object literal is valid OR use [Symbol.iterator]

    Now you may connect this to conclude the Approach 3 which passes your complete object.

    Hope this helps!