skip to Main Content

I have a Mongoose model like this:

const centerSchema = mongoose.Schema({
  centerName: {
    type: String,
    required: true,
  },
  candidates: [
    {
      candidateName: String,
      voteReceived: {
        type: Number,
        default: 0,
      },
      candidateQR: {
        type: String,
        default: null,
      },
    },
  ],
  totalVote: {
    type: Number,
    default: 0,
  },
  centerQR: String,
});

I have a Node.JS controller function like this:

exports.createCenter = async (req, res, next) => {
  const newCenter = await Center.create(req.body);

  newCenter.candidates.forEach(async (candidate, i) => {
    const candidateQRGen = await promisify(qrCode.toDataURL)(
      candidate._id.toString()
    );
    candidate.candidateQR = candidateQRGen;

    // ** Tried these: **
    // newCenter.markModified("candidates." + i);
    // candidate.markModified("candidateQR");
  });

  // * Also tried this *
  // newCenter.markModified("candidates");
  const upDatedCenter = await newCenter.save();
  res.status(201).json(upDatedCenter);
};

Simply, I want to modify the candidateQR field on the subdocument. The result should be like this:

{
    "centerName": "Omuk Center",
    "candidates": [
        {
            "candidateName": "A",
            "voteReceived": 0,
            "candidateQR": "some random qr code text",
            "_id": "624433fc5bd40f70a4fda276"
        },
        {
            "candidateName": "B",
            "voteReceived": 0,
            "candidateQR": "some random qr code text",
            "_id": "624433fc5bd40f70a4fda277"
        },
        {
            "candidateName": "C",
            "voteReceived": 0,
            "candidateQR": "some random qr code text",
            "_id": "624433fc5bd40f70a4fda278"
        }
    ],
    "totalVote": 0,
    "_id": "624433fc5bd40f70a4fda275",
    "__v": 1,
}

But I am getting the candidateQR still as null in the Database. I tried markModified() method. But that didn’t help (showed in the comment section in the code above). I didn’t get any error message. In response I get the expected result. But that result is not being saved on the database. I just want candidateQR field to be changed. But couldn’t figure out how.

4

Answers


  1. Chosen as BEST ANSWER

    forEach loop was the culprit here. After replacing the forEach with for...of it solved the issue. Basically, forEach takes a callback function which is marked as async in the codebase which returns a Promise initially and gets executed later.

    As for...of doesn't take any callback function so the await inside of it falls under the controller function's scope and gets executed immediately. Thanks to Indraraj26 for pointing this out. So, the final working version of the controller would be like this:

    exports.createCenter = async (req, res, next) => {
      const newCenter = await Center.create(req.body);
    
      for(const candidate of newCenter.candidates) {
        const candidateQRGen = await promisify(qrCode.toDataURL)(
          candidate._id.toString()
        );
        candidate.candidateQR = candidateQRGen;
      };
    
      newCenter.markModified("candidates");
      const upDatedCenter = await newCenter.save();
      res.status(201).json(upDatedCenter);
    };
    

    Also, shoutout to Moniruzzaman Dipto for showing a different approach to solve the issue using async.eachSeries() method.


  2. As far as I understand, you are just looping through the candidates array but you
    are not storing the updated array. You need to store the updated data in a variable as well. Please give it a try with the solution below using map.

    exports.createCenter = async (req, res, next) => {
      const newCenter = await Center.create(req.body);
    
      let candidates = newCenter.candidates;
      
      candidates = candidates.map(candidate => {
          const candidateQRGen = await promisify(qrCode.toDataURL)(
              candidate._id.toString()
          );
    
          return {
              ...candidate,
              candidateQR: candidateQRGen  
          }
      });
    
      newCenter.candidates = candidates;
    
      const upDatedCenter = await newCenter.save();
    
      res.status(201).json(upDatedCenter);
    };
    
    Login or Signup to reply.
  3. You can use this before save()

    newCenter.markModified('candidates');
    
    Login or Signup to reply.
  4. You can use eachSeries instead of the forEach loop.

    const async = require("async");
    
    exports.createCenter = async (req, res, next) => {
      const newCenter = await Center.create(req.body);
    
      async.eachSeries(newCenter.candidates, async (candidate, done) => {
        const candidateQRGen = await promisify(qrCode.toDataURL)(
          candidate._id.toString(),
        );
        candidate.candidateQR = candidateQRGen;
        newCenter.markModified("candidates");
        await newCenter.save(done);
      });
    
      res.status(201).json(newCenter);
    };
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search