DEV Community

milesbd
milesbd

Posted on

Updating Nested Fields in Firestore with dot notation

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:

{
  "Questions":{
    "Q11":{
      "Paper":{
        "EF":0.2811,
        "amount":5002,
        "name":"Paper",
      },
      "Furniture":{
        "EF":0.3677,
        "amount":400,
        "name":"Furniture"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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:

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>
  );
};
Enter fullscreen mode Exit fullscreen mode

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" 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:

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);
      });
  };
Enter fullscreen mode Exit fullscreen mode

And wouldn't you know it, it worked!

My full component then became:

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>
  );
};

Enter fullscreen mode Exit fullscreen mode

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 :)

Top comments (0)