Updating object in react js in a function based component

Viewed 51

I have this imaginary case where I have a function that has a form, which I can update my data. It is a real estate agency and wants to have option to update its properties when the sellers makes some changes. Note that data might not make much sense irl but it is just for learning purpose.

A property object looks like this:

  {
    owner: "John doe",
    other: {
      rooms: 3,
      windows: 6
    }
    address: {
      street: "jane doe",
      houseNr: 24
    }
  }

And my component:

import React, { useState } from 'react'
import { connect } from 'react-redux'
import { getProperty } from '../../actions'

const Property = ({ getProperty, property }) => {
  const [propertyObj, setPropertyObj] = useState(property);

  const { propertyId, owner, address, other } = propertyObj
  const propertyAddress = `${address.street},  ${address.houseNr}`  // jane doe, 24
  const { rooms } = other;

  const handleSubmit = (event) => {
    event.preventDefault()
    // todo: submit the updated property
  }
  return (
    <div className='row align-items-start'>
      <div className='col col-md-6'>
        <p>Id: {propertyId}</p>
        <form>
          <div className='row'>
            <label className='mt-3'>Owner</label><input className='ml-3' type='text' name='owner' onChange={e => setPropertyObj({ owner: e.target.value })} defaultValue={owner} />
          </div>
          <div className='row'>
            <label className='mt-3 mx-3'>Rooms</label><input className='ml-3' type='text' name='rooms' onChange={e => setPropertyObj({ rooms: e.target.value })} defaultValue={rooms} />
          <div className='row'>
            <label className='mt-3 mx-3'>Property Address</label><input className='ml-3' type='text' name='propertyAddress' onChange={e => setPropertyObj({ propertyAddress: e.target.value })} defaultValue={propertyAddress} />
          </div>
        </form>
      </div>
      <button onClick={(e) => handleSubmit(e)} className='btn btn-success'>Update Property</button>
    </div>
  )
}

const mapStateToProps = reduxStoreState => {
  return {
    property: reduxStoreState.property,
  }
}

export default connect(mapStateToProps, { getProperty })(Property)

When attempting to update rooms property, I get:

TypeError: other is undefined

But I beliver I would get a similar error when I would try to update the property address. I know that for property address, I have to split into street and house number to update each separately. The question is how can I update the state variable property so that I can submit that to my function updatePropertyById(id, updatedProperty), I have seen ways to do in cases when it is a class object, but I want to use the new features of react. How can I update the rooms for instance, without needing to also type the windows, I think the spread operator would be helpful but I just don't know how.

2 Answers

Updating a json object via spread operator

To use the spread operator, you have to know the level of the key. Lets assume we have an object:

const obj = {"field1": {"field2": 1, "field3": 2}, "field4":3}

To update field3 you have to do:

obj = {...obj, field1: {...field1, field3: newValue}}

In your case

This is a little different in your case, since you want to give the key and update the object. One way of doing this is traversing the object

const updateProperty = (obj, key, newVal) => {
  let tempObj = {...obj}
  Object.keys(tempObj).forEach(k => {
    if(k===key) tempObj[k] = newVal
  })
  setPropertyObj(tempObj)
}

In your code, you've written:

...
onChange={e => setPropertyObj({ rooms: e.target.value })}
...

This overrides the previous value of propertyObj. What you need to do is to keep the previous state using the spread operator:

onChange={e => setPropertyObj(state => ({ ...state, rooms: e.target.value })}

Or even better, you can write a function for this:

function handleChange(e) {
  setPropertyObj(state => ({
    ...state, 
    [e.target.name]: e.target.value
  })
}

...
<input className='ml-3' type='text' name='rooms' onChange={handleChange} defaultValue={rooms} />
...

UPDATE: I think by other, you meant to use the rest syntax in object destructring. The code { other } = propertyObj means that you're looking for a property with key other inside propertyObj. If you want to assign the remaining properties to a newly defined variable, you should prepend it with three dots:

const { propertyId, owner, address, ...other } = propertyObj
Related