0

I want to render a 'card' with certain details pulled from a MongoDB database, and passed in as props to a React front-end. The back-end is a serverless function.

Currently, I can't get my React code to find my MongoDB entries. It keeps timing out with the below error message:

2020-10-12T15:25:11.119Z    cde99f57-0da2-4b71-9fc1-d2eda3c1369c    ERROR   (node:8) DeprecationWarning: current Server Discovery and Monitoring engine is deprecated, and will be removed in a future version. To use the new Server Discover and Monitoring engine, pass option { useUnifiedTopology: true } to the MongoClient constructor.
2020-10-12T15:25:21.062Z cde99f57-0da2-4b71-9fc1-d2eda3c1369c Task timed out after 10.01 seconds

This happens after every single reload of the page - the request keeps timing out.

Can anyone point out what I'm doing wrong here?

Here's my code...

Schema:

const mongoose = require("mongoose");
const Schema = mongoose.Schema;


const SubmitDebtSchema = new Schema ({
  creditCard: String,
  personalLoan: String,
  provider: String,
  balance: Number,
  limit: Number,
  monthly: Number,
  interest: Number,
  borrowed: Number
});

module.exports = mongoose.model('submitdebts', SubmitDebtSchema);

My 'fetch' API function:

const express = require("express");
const mongoose = require("mongoose");
const bodyParser = require("body-parser");
const SubmitDebt = require("./submitDebtSchema");

require("dotenv").config();

const app = express();

app.use(bodyParser.urlencoded({
  extended: true
}));

mongoose.connect(`mongodb+srv://${process.env.MONGO_USER}:${process.env.MONGO_PASSWORD}@otter-money.f9twk.mongodb.net/test?retryWrites=true&w=majority`, {useNewUrlParser: true, useUnifiedTopology: true});


module.exports = async (req, res) => {

  res.statusCode = 200;
  res.setHeader("Content-Type", "application/json");

  await SubmitDebt.find({});
};

My React code:

class IndividualDebts extends Component {
  constructor(props) {
    super(props)

    this.state = {
      debts: {}
    }

    this.getDebtCards = this.getDebtCards.bind(this);
    this.renderDebtCards = this.renderDebtCards.bind(this);
  }

  componentDidMount = () => {
    this.getDebtCards()
  }

  getDebtCards = () => {
    axios.get("/api/fetchDebtCards")
      .then((res) => {
      const data = res.data
      this.setState ({
        debts: data
      })
      console.log("Data has been received.")
    })
    .catch((error) => {
      alert("Error fetching the data.")
      console.log(error)
    })
  }

  renderDebtCards = (debts) => {
    if (!this.state.debts.length) {
      return null
    }

    this.state.debts.map((debt, index) => {
      return (
        <div>
        <IndividualDebtCard key={index}
          provider={debt.provider}
          type={debt.creditCard === 'true' ? debt.creditCard : "Personal Loan" }
          balance={debt.balance}
          limit={debt.creditCard === 'true' ? debt.limit : debt.borrowed}
          monthly={debt.monthly}
          interest={debt.interest} />
        </div>
      )
    })
  }


  render() {
    return (
      <div>
        <section className="individual-debts-section">
          <div className="individual-debts-container">
            <div className="individual-debts-heading">
              <h3>Individual Breakdown</h3>
            </div>

            <div className="individual-debts-card-container">

              {this.renderDebtCards()}

            </div>

I feel like it's really close, but I just can't figure out what I'm doing wrong!! Any tips?

Thank you

Jon Nicholson
  • 927
  • 1
  • 8
  • 29
  • What would the method be for an object? The mongoose documentation only states find. – Jon Nicholson Oct 12 '20 at 09:27
  • Tried adding '.default' on, it's now made 'SubmitDebt' be anonymous in the error logs. The documentation I'm referring to is this as well: https://mongoosejs.com/docs/api.html#model_Model.find – Jon Nicholson Oct 12 '20 at 10:33
  • Hey - thanks again for the response. Really appreciate it. I don't really understand how I'd need to re-factor my code in line with your latest response. Would you be able to add an answer for me so I can see? – Jon Nicholson Oct 12 '20 at 14:11
  • Please check this out: https://stackoverflow.com/questions/34241970/mongoose-model-find-is-not-a-function – Aniruddha Shevle Oct 12 '20 at 14:56
  • Thanks for the feedback.. I've converted my code now to reflect the answers. It's still not working but will update my question to reflect the new error message. – Jon Nicholson Oct 12 '20 at 15:23
  • Why don't you search for such questions first and then post/edit the question if need be? https://stackoverflow.com/questions/57895175/server-discovery-and-monitoring-engine-is-deprecated ? – Aniruddha Shevle Oct 12 '20 at 15:53
  • That’s not the problem. There’s two error messages - it’s the second time out one which is the problem like I put in the updated post :-) – Jon Nicholson Oct 12 '20 at 16:09

2 Answers2

1

Several problems with your API setup...

Problems with Solutions

Adjust your submitdebts model to return the current registered model or to register the schema with the mongoose connection:

const mongoose = require("mongoose");
const Schema = mongoose.Schema;

...Schema

module.exports = mongoose.models.submitdebts || mongoose.model('submitdebts', SubmitDebtSchema);

If your front-end and back-end are running on separate processes, you'll need to add CORS headers (I like to use this package for CORS):

/*
 Either add CLIENT to your .env files:
 
 CLIENT: http://example.com (for production)
 CLIENT: http://localhost:3000 (for development)
 ...etc

 or add it to your your startup script in the package.json:
 
 for example: "dev": "NODE_ENV=development CLIENT=http://localhost:3000 node server.js"

  This is important to allow requests from the client
*/
...required imports
const cors = require("cors");

const { CLIENT, ...etc } = process.env;

...startup script

app.use(
  cors({ 
    origin: CLIENT 
  })
);

...listen to port

mongoose.connect is asynchronous, so you currently have a racing condition when connecting and starting up your server. Instead, you should wait for the connection to resolve, then start up your express server. Also, your controller to retrieve the documents isn't responding correctly to the client's request:


const { CLIENT, MONGO_USER, MONGO_PASSWORD, PORT } = process.env;

const options = {
  useNewUrlParser: true, // avoids DeprecationWarning: current URL string parser is deprecated
  useCreateIndex: true, // avoids DeprecationWarning: collection.ensureIndex is deprecated.
  useFindAndModify: false, // avoids DeprecationWarning: collection.findAndModify is deprecated.
  useUnifiedTopology: true // avoids DeprecationWarning: current Server Discovery and Monitoring engine is deprecated
};

(async () => {
  try {
    // if this fails to connect after 10 seconds, it'll kill the process
    // so make sure your credentials and host URL are correct!!!!!
    await mongoose.connect(`mongodb+srv://${MONGO_USER}:${MONGO_PASSWORD}@otter-money.f9twk.mongodb.net/test?retryWrites=true&w=majority`, options);

    const app = express();

    app.use(bodyParser.urlencoded({ extended: true }));
   
    // this now tells express to accept requests from the client
    app.use(
      cors({ 
        origin: CLIENT 
      }) 
    );

    // this will accept any GET requests to "/api/fetchDebtCards" 
    // and respond with "debts" (can be named anything)
    app.get("/api/fetchDebtCards", async (req, res) => {
      try {
        const debts = await SubmitDebt.find({});
    
        res.status(200).json({ debts });
        /*
          Optionally, you can use:
          
          res.status(200).send(debts) 
 
          which will allow "debts" to be directly accessible from "res.data"
        */ 
      } catch (error) {
        res.status(400).json({ error });
      }
    });

    app.listen(PORT, (err) => {
      if (err) throw err;
      console.log(`Listening for requests from: \x1b[1m${CLIENT}\x1b[0m\n`);
    });
  } catch (err) {
    console.log(err.toString());
    process.exit(1);
  }
})();

Now on the client, you should be able to retrieve the "debts" documents from res.data.debts (I prefer async/await over thenables, but up to you):

   getDebtCards = async () => {
     try {
       const res = await axios.get("/api/fetchDebtCards");

       console.log("Data has been received: ", JSON.stringify(res.data.debts, null, 4))

       this.setState({ debts: res.data.debts });
     } catch(error) {
       alert("Error fetching the data.")

       console.log(error)
     }
  }

Code

server.js

require("dotenv").config();
const bodyParser = require("body-parser");
const cors = require("cors");
const express = require("express");
const mongoose = require("mongoose");
const SubmitDebt = require("./submitDebtSchema");

const { CLIENT, MONGO_USER, MONGO_PASSWORD, PORT } = process.env;

const options = {
  useNewUrlParser: true,
  useCreateIndex: true, 
  useFindAndModify: false, 
  useUnifiedTopology: true
};

(async () => {
  try {
    await mongoose.connect(`mongodb+srv://${MONGO_USER}:${MONGO_PASSWORD}@otter-money.f9twk.mongodb.net/test?retryWrites=true&w=majority`, options);

    const app = express();

    app.use(bodyParser.urlencoded({ extended: true }));
   
    app.use(
      cors({ 
        origin: CLIENT 
      }) 
    );

    app.get("/api/fetchDebtCards", async (req, res) => {
      try {
        const debts = await SubmitDebt.find({});

        res.status(200).json({ debts });
      } catch (error) {
        res.status(400).json({ error });
      }
    });

    app.listen(PORT, (err) => {
      if (err) throw err;
      console.log(`Listening for requests from: \x1b[1m${CLIENT}\x1b[0m\n`);
    });
  } catch (err) {
    console.log(err.toString());
    process.exit(1);
  }
})();

Conclusions

You should be able to spin up just the express server and query against it using Postman. For example, I'm running a development express server at http://localhost:5000 and I'll query with GET http://localhost:5000/api/users and I'll get back a JSON response of users: enter image description here

Matt Carlotta
  • 18,972
  • 4
  • 39
  • 51
  • Appreciate the detailed response. This code will inevitably be a lot more thorough and effective than mine, but in the end it turned out I had a problem with my Axios query, and I didn't return the correct JSON in my Fetch API file. I'll accept this but not used it - thanks for your time! – Jon Nicholson Oct 16 '20 at 12:44
-1

You're not giving any parameters when calling your this.rennderDebtCards methods in your JSX. Either pass this.state.debts, or use this.state.debts directly inside your method

renderDebtCards = () => {
    if (this.state.debts.length === 0) {
      return null
    }

    return this.state.debts.map((debt, index) => {
      return (
        <div>
        <IndividualDebtCard key={index}
          provider={debt.provider}
          type={debt.creditCard === 'true' ? debt.creditCard : "Personal Loan" }
          balance={debt.balance}
          limit={debt.creditCard === 'true' ? debt.limit : debt.borrowed}
          monthly={debt.monthly}
          interest={debt.interest} />
        </div>
      )
    })
  }
GitGitBoom
  • 1,822
  • 4
  • 5
Chandelier Axel
  • 237
  • 2
  • 6
  • Hey - I've tried this but unfortunately it's not worked. I'm getting a separate error message in my function logs for 'SubmitDebt.find({}) is not a function'. – Jon Nicholson Oct 12 '20 at 08:58
  • I think it mean that you're trying to execute a mongo query on something that is not allowed to. Check if your SubmitDebt is properly imported and is your mongo model – Chandelier Axel Oct 12 '20 at 09:19
  • Hey - I’ve done this. I think it’s okay, but I’ve added all of my code to be safe. If you’ve got any feedback on the updated question that would be really appreciated! – Jon Nicholson Oct 12 '20 at 09:57