0

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!

10
  • 1
    You can only use the someFunction(...localEmailData) syntax if localEmailData is an array, not an object. This is how you are trying to use it in "approach 2", so the error is absolutely expected, and I'm amazed that you say it "works". Approach 1 though passes an array of the values (try console.log(Object.Values(localEmailData)) to see this), which is perfectly fine to use ... on in this way. Note that you don't actually need to shallow copy with ... here: actions.updateEmail(Object.values(localEmailData)) will do exactly the same (Object.values returns a new reference). Commented Nov 25, 2023 at 17:19
  • As to which, if any, of these are actually correct - that depends on what your updateEmail is doing. You've shown us the code but that isn't at all clear without context. Still, there's enough there to make all of your approaches look wrong - firstly it only takes one argument (newData), whereas spreading an array will pass it many arguments, so your function will ignore all but the first of those. (And with Object.values you have no practical idea which order the values will be in.) Also it does ...newData inside an object literal, so that is expected to be an object anyway. Commented Nov 25, 2023 at 17:22
  • Hi, Robin. I say that it "does work" as, regardless of the error thrown, the output on the next screen is what is expected, if that makes any sense. Obviously, it doesn't work as there's an error thrown and, thus, this very consultation that I am doing to the community. I ain't no expert, and English is not my native tongue, so I am doing my best at both working with React as well as trying to explain myself 😅 Thanks for the patience 😇 Cheers! 🥂 Commented Nov 26, 2023 at 18:15
  • There is no "copying" going on at all when using spread syntax in a function call? Commented Nov 27, 2023 at 18:12
  • Hi, @Bergi :-) Is that a question? If so, I don't understand it 😅 Commented Nov 27, 2023 at 18:22

2 Answers 2

1

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!

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks a lot, Sooraj. I think it's clear now, although I'd need more and more practice to work out all the little details regarding all of the information you've provided me with. Thanks so much for the help! Cheers! 🥂
0

I guess that I've understood a bit more on how objects behave with this experience, which I believe that can be summarized like so:

I've got an object:

let object = { key1:"blue", key2:"red", key3:"yellow" } 

Initially, if I copy it either with a spread operator or without it, I've got the very same object:

let copiedObjectWithSpreadOperator = {...object}; let copiedObjectWithoutSpreadOperator = object console.log("Object Copied With Spread Operator", copiedObjectWithSpreadOperator) console.log("Object Copied Without Spread Operator", copiedObjectWithoutSpreadOperator) 

This is, in both cases, console yields:

Object Copied With Spread Operator { key1: 'blue', key2: 'red', key3: 'yellow' } Object Copied Without Spread Operator { key1: 'blue', key2: 'red', key3: 'yellow' } 

And, when copied with Object.Values...

let copiedObjectWithObjectValues = Object.values(object) console.log("Object Copied With Object.Values()", copiedObjectWithObjectValues) 

Console yields...

Object Copied With Object.Values() [ 'blue', 'red', 'yellow' ] 

Now, the key to all the understanding I think I've achieved come from changing the value of a property like so:

object.key1 = "green" 

Now, when repeating the same console.logs:

console.log("Original Object:", object) console.log("Object Copied With Spread Operator", copiedObjectWithSpreadOperator) console.log("Object Copied Without Spread Operator", copiedObjectWithoutSpreadOperator) console.log("Object Copied With Object.Values()", copiedObjectWithObjectValues) 

Console yields...

Original Object: { key1: 'green', key2: 'red', key3: 'yellow' } Object Copied With Spread Operator { key1: 'blue', key2: 'red', key3: 'yellow' } Object Copied Without Spread Operator { key1: 'green', key2: 'red', key3: 'yellow' } Object copied with Object.Values() [ 'blue', 'red', 'yellow' ] 

We can see how the original object has changed, as so has done the one copy made without spread operator. This is, is that copy is not even a copy, is just a new reference that points to the original object.

On the other hand, the copy made with the spread operator hasn't changed when changing a value of the initial object as it is a new object itself.

Now, it all makes an awful lot of sense.

Thanks!

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.