Updating Nested Fields in Firestore with dot notation
Tags: firebase, react, javascript
Published: 2021-07-26 07:36:29

## The context
While developing an extended questionnaire in a React + Rirebase (Firestore as the db) project, I found myself facing looking for a way to write to a field nested in object. 

These extended questions were on a dashboard-style page, that had a listener to the user's profile so that it could receive realtime updates and display their data as they modified it. As the listener was present, I was looking to do the smallest write possible, to limit the number of components re-rendering. 

The question that I was looking to modify structured like this:

```json
{
  "Questions":{
    "Q11":{
      "Paper":{
        "EF":0.2811,
        "amount":5002,
        "name":"Paper",
      },
      "Furniture":{
        "EF":0.3677,
        "amount":400,
        "name":"Furniture"
      }
    }
  }
}
```
Where the field to be changed was the `amount` field. On the page, the questions were presented as inputs, which fire the onChange event as soon as there has been a change to the field - ideal in this case but again making it all the more necessary to make writes as minimal as possible to avoid re-renders. 

The initial component:
```javascript
const ExtendedQuestions = (props) => {
  const { Q11 } = props;
  const [loading, setLoading] = React.useState(null);

  return (
    <React.Fragment>
      {Q11.map((question, i) => {
        const { name, EF, amount } = question;
        return (
          <input
            key={`ExtendedField_${i}`}
            inline
            label={name}
            type="number"
            name={name}
            ef={EF}
            value={amount}
            loading={name === loading}
            min={0}
            max={100000}
            step={1}
          />
        );
      })}
    </React.Fragment>
  );
};
```


## Initial discovery
With the goal in mind of having the onChange event on the form trigger a write to the user's profile modifying their answer, I dove into the  Firebase docs looking for a solution. While reading through the docs, I came across the small section on using ["dot notation"](https://firebase.google.com/docs/firestore/manage-data/add-data#update_fields_in_nested_objects) to do writes to nested objects. This solution seemed ideal, however it seemed that docs required the "dot notation" to be formatted as a string. My first thought, given that my component was written simply, was that I would need to write a function for each input.

## The solution
After searching for ways to dynamically set the string for the "dot notation" without finding much in the way of answers, I figured I would simply try creating the object with bracket notation, (i.e. `[pathInDotNotation]` and test the result.

I wrote it out my onChange function as follows:

```javascript
const handleChange = (e, data) => {
    const { name, ef, value } = data;
    setLoading(name);
    firebase
      .collection("users")
      .doc(`${authUser}`)
      .update({
        [`Questions.Q11.${name}`]: {
          text: name,
          amount: Number(value),
          EF: Number(ef),
        },
      })
      .then(() => {
        setLoading(null);
      })
      .catch((error) => {
        console.error("Error updating document: ", error);
      });
  };
```

And wouldn't you know it, it worked!

My full component then became:
```javascript
const ExtendedQuestions = (props) => {
  const { Q11 } = props;
  const [loading, setLoading] = React.useState(null);

  const handleChange = (e, data) => {
    const { name, ef, value } = data;
    setLoading(name);
    firebase
      .collection("users")
      .doc(`${authUser}`)
      .update({
        [`Questions.Q11.${name}`]: {
          text: name,
          amount: Number(value),
          EF: Number(ef),
        },
      })
      .then(() => {
        setLoading(null);
      })
      .catch((error) => {
        console.error("Error updating document: ", error);
      });
  };

  return (
    <React.Fragment>
      {Q11.map((question, i) => {
        const { name, EF, amount } = question;
        return (
          <input
            key={`ExtendedField_${i}`}
            inline
            label={name}
            type="number"
            name={name}
            ef={EF}
            value={amount}
            loading={name === loading}
            min={0}
            max={100000}
            onChange={handleChange}
            step={1}
          />
        );
      })}
    </React.Fragment>
  );
};

```

This was great as my component was now able to only write to the field that needed changing. Additionally, as I was using a map function to render all the questions with keys, I was able to keep re-renders to a minimum, only targeting the actual input that was changed.

Thanks for reading! I'd appreciate any feedback you may have :)