When we work with a set of related variables, it can be very helpful to group them in an object. Let’s look at an example!

export default function Login() { const [formState, setFormState] = useState({}); const handleChange = ({ target }) => { const { name, value } = target; setFormState((prev) => ({ ...prev, [name]: value })); }; return ( <form> <input value={formState.firstName} onChange={handleChange} name="firstName" type="text" /> <input value={formState.password} onChange={handleChange} type="password" name="password" /> </form> ); }

A few things to notice:

  • We use a state setter callback function to update state based on the previous value
  • The spread syntax is the same for objects as for arrays: { ...oldObject, newKey: newValue }
  • We reuse our event handler across multiple inputs by using the input tag’s name attribute to identify which input the change event came from

Once again, when updating the state with setFormState() inside a function component, we do not modify the same object. We must copy over the values from the previous object when setting the next value of state. Thankfully, the spread syntax makes this super easy to do!

Anytime one of the input values is updated, the handleChange() function will be called. Inside of this event handler, we use object destructuring to unpack the target property from our event object, then we use object destructuring again to unpack the name and value properties from the target object.

Inside of our state setter callback function, we wrap our curly brackets in parentheses like so: setFormState((prev) => ({ ...prev })). This tells JavaScript that our curly brackets refer to a new object to be returned. We use ..., the spread operator, to fill in the corresponding fields from our previous state. Finally, we overwrite the appropriate key with its updated value. Did you notice the square brackets around the name? This Computed Property Name allows us to use the string value stored by the name variable as a property key!



Throughout our JSX, we are looking up properties stored on the profile object. On the first render, this is a problem because attempting to get the value of a property from an object that has not been defined causes JavaScript to throw an error.

To defend against these errors, let’s initialize profile as an empty object!


Add the event listeners to our JSX tags to call handleChange() whenever a user types in an input field.


Let’s make our handleChange() function a bit easier to read. Use object destructuring to initialize name and value in a more concise way.


There’s a bug in our code! Have you noticed it? Try typing in one input, then type in a different input. What happens? Why?

Each time that we call setProfile() in our event handler, we give profile the value of a new object with the name and value of the input that most recently changed, but we lose the values that were stored for inputs with any other name.

Let’s use the spread operator to fix this bug. We want to copy over all of the values from our previous profile object whenever we call our state setter function. Use prevProfile as the argument for our state setter callback function.


Add an event listener to call our handleSubmit() function when the user submits the form.

Take this course for free

Mini Info Outline Icon
By signing up for Codecademy, you agree to Codecademy's Terms of Service & Privacy Policy.

Or sign up using:

Already have an account?