0

I have a React component that has some form fields in it:

<TextField
  label="Description"
  id="description"
  value={company.companyDescription}
  onChange={updateFormField("companyDescription")}
></TextField>

and a function that updates my company state whenever the values change:

const updateFormField = (property: string) => (event: ChangeEvent<HTMLInputElement>) => {
  setCompany(prev => ({ ...prev, [property]: event.target.value }))
}

This means that whenever a form field changes I'd like to create a new copy (hence the spread operator) of the old object.

My problem is that company has nested properties in it, like company.location.address:

{
  name: "some company",
  location: {
    address: "some street"
  }
  // ...
}

Is there a way to refactor the above function to update the nested properties? For example something like:

const updateFormField = (property: string[]) => (event: ChangeEvent<HTMLInputElement>) => {
  setCompany(prev => ({ ...prev, [...property]: event.target.value }))
}
2
  • Do you mean that you want to merge the old/new objects for your company[property]? Commented Jun 22, 2021 at 12:40
  • I've updated my answer. I'm creating a new object every time (this is how React works). I'd like to overwrite specific properties, like company.name or company.location.address. Commented Jun 22, 2021 at 12:43

2 Answers 2

1

I don't think there's a particularly neat solution to this, however it should be possible to loop through selecting/adding the new path:

const updateFormField = (property: string[]) => (event: ChangeEvent<HTMLInputElement>) => {
  setCompany(prev => {
    // Take a copy of the state
    const newObj = { ...prev }
    // Set up a refernce to that object
    let selected = newObj
    // Loop through each property string in the array
    for (let i = 0; i < property.length; i++) {
        // If we're at the end of the properties, set the value
        if (i === property.length - 1) {
            selected[property[i]] = event.target.value
        } else {
            // If the property doesn't exist, or is a value we can't add a new property to, set it to a new object
            if (typeof selected[property[i]] !== 'object') {
                selected[property[i]] = {}
            } 
            // Update our refernce to the currently selected property and repeat
            selected = selected[property[i]]
        }
    }
    // Return the object with each nested property added
    return newObj
  )}
}

Plain JS working example of the same method:

const test = (prev, property, value) => {
  const newObj = { ...prev
  }
  let selected = newObj
  for (let i = 0; i < property.length; i++) {
    if (i === property.length - 1) {
      selected[property[i]] = value
    } else {
      if (typeof selected[property[i]] !== 'object') {
        selected[property[i]] = {}
      }
      selected = selected[property[i]]
    }
  }
  return newObj
}

console.log(test(
  {"a": "1"},
  ["b", "c", "d"],
  100
))

console.log(test(
  {"a": "1"}, 
  ["a", "b"], 
  100
))

console.log(test(
  {"a": {"b": {"c": 1}}},
  ["a", "b", "c"],
  100
))

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

2 Comments

Thanks for your effort. I suspected that this was the case. :(
No problem, hopefully someone else can answer with an operator/pattern I don't know about, but unfortunately I've not come across one.
1

Object.assign() and dynamically finding the inner reference should do it. I'm assuming the input type of string[] above indicates the nested path is an array like ['company', 'location', 'address']:

const updateFormField = (property: string[]) => (event: ChangeEvent<HTMLInputElement>) => {
  setCompany(prev => {
    const copy = Object.assign({}, prev);
    let ref = copy;
    for (let i = 0; i < property.length - 1; i++) {
      ref = ref[property[i]];
    }
    ref[property[property.length - 1]] = event.target.value
    return copy;
  });
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.