Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mongoose $push keeps adding two entries

Here are my user and product schemas:

const productSchema = new Schema({
  //... 
  addedBy: {
    type: mongoose.Schema.Types.ObjectId,
    ref: "users"
  }
});

const userSchema = new Schema({   
  //...
  addedItems: [{
    type: mongoose.Schema.ObjectId,
    ref: "products"
  }]
});

mongoose.model("products", productSchema);
mongoose.model("users", userSchema);

In my Node back end route I do this query:

User.findOneAndUpdate(
  { _id: req.body.id },
  { $push: { addedItems: newProduct._id } },
  { upsert: true, new: true },
  function(err, doc) {
    console.log(err, doc);
  }
);

The console.log prints out this:

{
    //...
    addedItems: [ 5ab0223118599214f4dd7803 ]
}

Everything looks good. I go to actually look at the data using the front-end website for my mongo db; I'm using mlab.com, and this is what shows:

{
//...
"addedItems": [
        {
            "$oid": "5ab0223118599214f4dd7803"
        },
        {
            "$oid": "5ab0223118599214f4dd7803"
        }
    ]
}

Question: What the heck happened? Why does it add an additional entry into addedItems ?! Even though my console.log only showed one.

Note:

I tested to see if the backend route was being called more than once. It is not.

It seems to be a problem with $push because if I just have { addedItems: newProduct._id } then only one entry goes in, but it overwrites the entire array.

Edit:

Made a test project to produce the same results: https://github.com/philliprognerud/test-mcve-stackoverflow

Can anyone figure out what's going on?

like image 545
Phillip Avatar asked Mar 19 '18 21:03

Phillip


People also ask

What is $Push in mongoose?

$push. The $push operator appends a specified value to an array. The $push operator has the form: { $push: { <field1>: <value1>, ... } } To specify a <field> in an embedded document or in an array, use dot notation.

What is Upsert true in mongoose?

May 20, 2019. In MongoDB, an upsert means an update that inserts a new document if no document matches the filter . To upsert a document in Mongoose, you should set the upsert option to the Model.

What is findById in mongoose?

In MongoDB, all documents are unique because of the _id field or path that MongoDB uses to automatically create a new document. For this reason, finding a document is easy with Mongoose. To find a document using its _id field, we use the findById() function.

What does findOneAndUpdate return mongoose?

By default, findOneAndUpdate() returns the document as it was before update was applied. You should set the new option to true to return the document after update was applied.


2 Answers

The problem is caused by your mixed used of promises (via async/await) and callbacks with the findOneAndUpdate call which ends up executing the command twice.

To fix the problem:

const updatedUser = await User.findOneAndUpdate(
  { id: userID },
  { $push: { addedItems: newProduct.id } },
  { upsert: true, new: true }
);

console.log(updatedUser);

Future readers note that the use of await isn't shown here in the question, but is in the MCVE.

like image 81
JohnnyHK Avatar answered Oct 11 '22 21:10

JohnnyHK


I am facing similar issue. Just landed to this page. I find that previous answer is not very descriptive. So posting this:

export const updateUserHandler = async (req, res) => {
    const request = req.body;    
 await  User.findOneAndUpdate(                  //<== remove await 
  { _id: request.id },
  { $push: { addedItems: newProduct._id } },
  { upsert: true, new: true },
  (findErr, findRes) => {
        if (findErr) {
          res.status(500).send({
            message: 'Failed: to update user',
            IsSuccess: false,
            result: findErr
          });
        } else {
          res.status(200).send({
            message: 'Success:  to update user',
            IsSuccess: true,
            result: findRes
          });

        }
      }
);
  }

Here there are two async calls one is the async and other is await. Because of this there are two entries in the document. Just remove await from await User.findOneAndUpdate. It will work perfectly. Thanks!!

like image 45
Ben P Avatar answered Oct 11 '22 19:10

Ben P