Fetching express data from react - reactjs

I'm trying to create a simple register form by preparing the data in the back end, and inserting the data from my react form into the mysql database. I know that there are many tutorials out there for how to do this however, I'm trying to do it by myself my own way.
Therefore, when I try to fetch the data from the back end, it throws me an error. saying the resource isn't found. On my server, when I put in the path to view the data it informs me that the path isn't found even though I required it?
Also, I believe I'm a little confused on the process of how data can be fetched over relative paths. How is this possible if React is using a different port than what the server is using? Does the fetch for relative paths go purely based on your folder location of the data you're trying to fetch?
For react the port I'm using is 3000
for and for the server I'm using 5000
Here is my code:
Model:
var db = require('../dbconnection');
var register = {
registerAuth: function(data, callback){
db.query("insert sanames (id, fullName, email, confirmEmail, password, confirmPassword) values(newid(), '"+data.fullName+"', '"+data.email+"', '"+data.confirmEmail+"', '"+data.password+"', '"+data.confirmPassword+"')")
}
}
// db.query('insert sanames (id, fullName, email, confirmEmail, password, confirmPassword, dateAdded) values(newid(), "'data.fullName'", "'data.email'", "'data.confirmEmail'", "'data.password'", "'data.confirmPassword')")',callback)
module.exports = register;
Route:
var express = require('express');
var router = express.Router();
var register = require('../models/register');
router.post('/:registerAuth', function(req, res, next) {
register.registerAuth(req.body,function(err, rows) {
if (err) {
res.json(err);
} else {
res.json(rows);
}
});
});
module.exports = router;
App (Server):
var express = require('express');
var bodyParser = require('body-parser');
var mysql = require('mysql');
var path = require('path');
var port = 5000;
var app = express();
//app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
app.use('/public', express.static(__dirname + "/public"));
var Register=require('./routes/register');
app.use('/register', Register);
app.listen(port, () => {
console.log(`Server is listening on ${port}`);
});
// app.get('/test', (req, res) => {
// res.send('Test');
// })
View:
import React from 'react';
import ReactDOM from 'react-dom';
import Header from '../../common/header';
class Register extends React.Component {
constructor(props){
super(props);
this.state = {
fullName: "",
email: "",
confirmEmail: "",
password: "",
confirmPassword: "",
formErrors: "",
success: ""
}
}
onChange(e){
this.setState({
[e.target.name]: e.target.value
});
};
onSubmit(e) {
// if(this.state.fullName !== '' || this.state.email || '' || this.state.confirmEmail !== '' || this.state.password !== '' || this.state.confirmPassword !== ''){
// if(this.state.password !== this.state.confirmPassword) {
// //console.log('passwords do not match');
// this.setState({
// formErrors: 'passwords do not match'
// });
// e.preventDefault();
// }
// if(this.state.email !== this.state.confirmEmail) {
// //console.log('email address must match');
// this.setState({
// formErrors: 'both email address must match'
// });
// e.preventDefault();
// }
// } else {
// //console.log('please fill out all fields');
// this.setState({
// formErrors: 'please fill out all fields'
// });
// e.preventDefault();
// }
e.preventDefault();
var data = {
fullName: this.state.name,
email: this.state.email,
confirmEmail: this.state.confirmEmail,
password: this.state.password,
confirmPassword: this.state.confirmPassword
}
fetch("/register", {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
}).then(function(response){
if(response.status >= 400) {
throw new Error("Bad response from server");
}
return response.json();
}).then(function(data){
console.log(data);
if(data == "success"){
this.setState({sucess: 'User has been successfully registered'});
}
}).catch(function(err){
console.log(err);
});
}
render(){
return (
<div className="background-video">
<Header />
<div className="login-container">
<div className="login-wrapper">
<div className="loginfields-wrap">
{this.state.formErrors !== '' ? <div className="alert alert-danger" role="alert">{this.state.formErrors}</div> : ''}
<form
onSubmit={e => this.onSubmit(e)}
autoComplete="off"
method="POST"
action="/registeruser"
>
<input
type="text"
name="fullName"
className="form-control"
placeholder="First/Last Name"
value={this.state.fullName}
onChange={e => this.onChange(e)}
/>
<input
type="email"
name="email"
className="form-control"
placeholder="Email"
value={this.state.email}
onChange={e => this.onChange(e)}
/>
<input
type="email"
name="confirmEmail"
className="form-control"
placeholder="Confirm Email"
value={this.state.confirmEmail}
onChange={e => this.onChange(e)}
/>
<input
type="password"
name="password"
className="form-control"
placeholder="Password"
value={this.state.password}
onChange={e => this.onChange(e)}
/>
<input
type="password"
name="confirmPassword"
className="form-control"
placeholder="Confirm Password"
value={this.state.confirmPassword}
onChange={e => this.onChange(e)}
/>
<button type="submit" className="btn btn">Register</button>
</form>
</div>
</div>
</div>
</div>
);
}
}
export default Register;

I hope my answer helps, its a bit tough to answer.
First thing I did was change the fetch to the following:
fetch("http://localhost:5000/register"
At first attempt I got a CORS issues, so I installed the npm package:
https://www.npmjs.com/package/cors
You can setup a proxy in your package JSON file which will help connect your React App to the express server - this is one way of how the app will connect to your express server - Its not that they are in the same folder, you can setup a server in a different folder and still connect to it, try it out - if it helps with understanding it more clearly.
"proxy": "http://localhost:5000"
Hope this helps

Related

MongoDB + React - Obtain a document from db based on a query I am passing

I am trying to access a document from my MongoDB Atlas db that contains a specific key, based on a query I am passing in the fetch. I've followed the guides on the backend setup from MongoDB, it's all working, I'm connected to it, and now here's what I'm trying to do:
Documents look like this:
{
invitationCode: string;
name: string;
numberOfPeople: number;
specialMentions: string;
confirmed: boolean;
}
In the frontend, there's only one input at first, where the user should be entering his invitation Code. Once he clicks on the button, a request should be made to the BE, sending the value he entered. The BE should look through the documents and find the document that contains the invitationCode mathing with the input (The invitation codes are all unique). After the BE identified the document, it should be sent back to the frontend, so I can juggle with it here (display the name of the person, show the other 3 fields, etc.)
Here's what I have so far:
in my record.js file (backend):
const express = require("express");
const recordRoutes = express.Router();
const dbo = require("../db/conn");
const ObjectId = require("mongodb").ObjectId;
recordRoutes.route('/record/invitations').post(function (req, res) {
let db_connect = dbo.getDb();
let myquery = req.body.invitationNumber;
console.log('MYQUERY:', myquery);
db_connect
.collection('records')
.findOne({zzceva: myquery}, function (err, result) {
if (err) throw err;
console.log('RESULT FROM BE', result);
res.send(result);
})
console.log('QUERY:', myquery);
})
and in the frontend I have this logic:
const onSubmit = useCallback(async (e) => {
e.preventDefault();
if (personEnteredCode) {
const newPerson = { ...form };
await fetch("http://localhost:5000/record/add", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(newPerson),
})
.catch(error => {
window.alert(error);
return;
});
setForm({ invitationNumber: "", numberOfPeople: "", specialMentions: "" });
navigate("/");
console.log('newPerson:', newPerson);
} else {
// THIS IS WHAT DOES NOT WORK >>>>>>>>>>>>>>>>>>>>>>>>>>>
// I AM TRYING TO SEND THE invCode back to the BE
const invCode = form.invitationNumber;
await fetch("http://localhost:5000/record/invitations", {
method: 'POST',
body: JSON.stringify(invCode),
})
.then((response) => {
console.log('THE RESPONSE IS:', response);
setCurrentPerson(response);
})
.catch(error => {
window.alert(error);
return;
})
.finally(() => setPersonEnteredCode(true))
}
// When a post request is sent to the create url, we'll add a new record to the database.
}, [form, navigate, personEnteredCode])
return (
<div className="confirm-form">
<form onSubmit={onSubmit}>
<div className="form-group">
<label htmlFor="invitationCode">Cod invitație:</label>
<input
type="text"
className="form-control"
id="invitationCode"
value={form.invitationNumber}
onChange={(e) => updateForm({ invitationNumber: e.target.value })}
/>
</div>
{!personEnteredCode && <input type={'submit'} value={'OK'}/>}
{personEnteredCode && <div className="form-group">
<label htmlFor="numberOfPeople">Număr persoane:</label>
<input
type="number"
className="form-control"
id="numberOfPeople"
value={form.numberOfPeople}
onChange={(e) => updateForm({ numberOfPeople: e.target.value })}
/>
</div>}
{personEnteredCode && <div className="form-group">
<div className="form-check form-check-inline">
<label htmlFor="specialMentions">Mențiuni speciale:</label>
<input
type="text"
className="form-control"
id="specialMentions"
value={form.specialMentions}
onChange={(e) => updateForm({ specialMentions: e.target.value })}
/>
</div>
</div>}
{personEnteredCode &&<div className="form-group">
<input
type="submit"
value="Confirmă"
className="btn btn-primary"
/>
</div>}
</form>
</div>
);
}
After many different tries, now the response I'm getting is 200 (not 404 not found or 500 like the first tries), but on the response object, I don't see the information I need, instead this is how a console.log looks like:
HUGE thanks in advance for any kind of guidance or help you could provide. I'm trying to understand what I'm doing wrong.
The issue is that you're logging the fetch response and not the data in the response body (so congrats! you're getting a response!).
The fetch response has a couple of different methods that you can use to read the data in the body. Depending on the type of data your API is returning, you'll use the appropriate method (.json, .text, .blob, etc.). These methods return a promise meaning they are asynchronous. Here's how you might modify your code:
fetch("http://localhost:5000/record/invitations", {
method: 'POST',
body: JSON.stringify(invCode)
})
.then((response) => {
return response.json()
})
.then((data) => {
//now you've got the data to put in state
setCurrentPerson(response);
})
.catch(error => {
window.alert(error);
return;
})
}
I can see that in your Express route, you're using res.send(result). You'll probably want to change that to: res.json(result). Both behave the same if you pass an object or array, but res.json() will explicitly convert your results to JSON.
Also, you didn't ask about it, but generally, you wouldn't use POST for this route. In REST, this would be a GET route and you'd generally pass the data as a param or querystring to your API.

React login form loop isn't re-rendering DOM

I'm trying to make a login component and I think my issue is with React not re-rendering the DOM in my browser but I'm not sure why
If I leave the password field blank when I press the main 'Login' button in my form it will render the alert / warning message .. I can then click this message to dismiss it which is exactly what I want
If I were to repeat the process I would expect the message to be re-rendered and the DOM element reintroduced, however this is not the case - I can see that the loop is being run, I am getting all of the console logs with the correct values, however the loop does not seem to run the 'return' part of my if statement on the second try (in the code below I've added 'this return doesn't re-render' to the console log before that return) - here's my code
Apologies for the large code snippet but I felt it was all relevant for this question
class LoginForm extends Component {
constructor() {
super();
this.state = {
email: "",
password: "",
errors: [],
};
this.onLoginClick = this.onLoginClick.bind(this);
}
onLoginClick() {
const username = this.state.email.trim();
const password = this.state.password.trim();
let errors = [];
console.log("Login press")
if (!EMAIL_REGEX.test(username)) {
errors.push(error_user);
console.log("Username error")
}
if (password === "") {
errors.push(error_pass);
console.log("Password is blank")
}
if (errors.length === 0) {
this.props.onLoginClick(username, password);
if (this.props.loginStatus === login_f) {
errors.push(error_cred);
}
}
this.setState({
errors: errors,
});
console.log("Here are the errors", errors)
}
handleEmailChange = (e) => {
this.setState({ email: e.target.value });
};
handlePasswordChange = (e) => {
this.setState({ password: e.target.value });
};
clearAlertsHandler() {
console.log("Clear alerts")
document.getElementById("misMatch").remove()
}
render() {
let updatedErrors = [...this.state.errors];
return (
<fieldset>
{updatedErrors.map((errorMessage, index) => {
if (errorMessage === error_cred) {
console.log("error_cred match", error_cred, errorMessage)
return (
<button key={index} id={"match"}>{errorMessage} - click to clear</button>
);
} else {
console.log("error_cred mismatch - this return doesn't re-render", error_cred, errorMessage)
return (
<button key={index} id={"misMatch"} onClick={(e) => this.clearAlertsHandler(e)}>{errorMessage} - click to clear</button>
);
}
})}
<label className="text-uppercase">Username</label>
<input
name="email"
type="text"
value={this.state.email}
placeholder="username"
onChange={this.handleEmailChange}
/>
<label className="text-uppercase">Password</label>
<input
className="mb20"
name="password"
type="password"
value={this.state.password}
placeholder="••••••••••"
onChange={this.handlePasswordChange}
/>
<button name="submit" className="primary mb20" onClick={this.onLoginClick}>
Login
</button>
</fieldset>
);
}
In my opinion, React doesn't know that error array changed if you don't clear it.
I think you should do something like this:
clearAlertsHandler() {
console.log("Clear alerts")
this.setState({
errors: [],
});
document.getElementById("misMatch").remove()
}

Deleting items in mongodb using reactjs doesn't work

I'm using reactjs as frontend and expressjs as backend.
the server got hacked with a low power ransomware but didn't affect the website files themselves but i had to add firewall on mongod instances to limit the other IPs to access the database and it all worked out great, after that i tried to add an item in the database using the frontend and it worked alright reading the data works and adding data works the problem was with the deleting when i delete using postman the request is sent and deletes the item ok, and when using firefox dev tools i edited my request to delete the specific item and it deleted it but when using frontend it doesn't do anything no request is sent doesn't give me any response no status codes no nothing.
The Frontend:
class AdminCardComp extends Component {
constructor(props) {
super(props);
this.state = {
appartmentId: ''
};
this.onChange = this.onChange.bind(this);
this.handleRemove = this.handleRemove.bind(this);
}
onChange(e) {
if (e.target.id === 'appartmentId') {
this.setState({ appartmentId: e.target.value });
}
}
handleRemove(){
this.props.delAppartment(this.state.appartmentId);
/*axios.delete("http://172.105.245.241:3443/appartments/"+this.state.ID,{ params: { appartmendId: this.state.ID }} , {}).then(res => {
console.log(res.data)
})*/
}
render() {
const appartmentRender = this.props.appartments.appartments.map((appartment) => {
var x = 1;
return(
<Card>
<CardImg top src={baseUrl + appartment.image[0].image} alt={appartment.name} />
<CardBody>
<CardTitle>Appartment Number: {x}</CardTitle>
<CardText>Appartment Description: {appartment.description}</CardText>
<CardText>Appartment Price: {appartment.price}</CardText>
</CardBody>
<CardFooter>
<CardText>App ID: {appartment._id}</CardText>
</CardFooter>
</Card>
);
x++;
})
return (
<>
<div className="container col-12">
<div className="row row-content col-12 col-sm-6">
{appartmentRender}
</div>
<div className="row row-content col-12 col-sm-6 justify-content-center">
<Form onSubmit={this.handleRemove}>
<Row>
<Col>
<Input className="formBack" onChange={this.onChange} type="text" id="appartmentId" name="appartmentId" placeholder="Enter ID" innerRef={(input) => this.appartmentId = input} />
</Col>
</Row>
<Row>
<Col>
<Button className="offset-sm-3 col-sm-5 buttonmr formBackButton" type="submit" value="submit">Remove</Button>
</Col>
</Row>
</Form>
</div>
</div>
</>
);
}
}
the delAppart method is in the ActionCreators file
export const delAppartment = (appartmentId) => (dispatch) => {
const bearer = 'Bearer' + localStorage.getItem('token');
return fetch(baseUrl + 'appartments/' + appartmentId ,{
method: "DELETE",
body: JSON.stringify({ "_id": appartmentId }),
headers: {
"Content-Type": "application/json"
},
credentials: "same-origin"
})
.then(response => {
if(response.ok){
return response;
}else {
var error = new Error('Error ' + response.status + ': ' + response.statusText);
error.response = response;
throw error;
}
},error => {
throw error;
})
}
given that everything worked before the attack, and as you see in the frontend i tried using axios and nothing has changed
The Backend
appartRouter.route('/:appartmentId')
.options((req,res) => {
res.sendStatus(200);
})
.get((req,res,next) =>{
Appartments.findById(req.params.appartmentId)
.then((appartment) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.json(appartment);
}, (err) => next(err))
.catch((err) => next(err));
})
.delete((req,res,next) =>{
Appartments.findByIdAndRemove(req.params.appartmentId)
.then((resp) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.json(resp);
}, (err) => next(err))
.catch((err) => next(err));
})
This is the networks tab when trying to remove an item i suspect the first one says it's a GET type and it should've been DELETE type i'm not sure
It seems to be a problem with CORS. I would recommend to read more about it here.
Also it would be helpful to have a screenshot or more info about the network request in the Network tab in Chrome Dev Tools.

INVALID_CLIENT: Invalid redirect URI error when running GitHub pages application. Uses Spotify API

I've deployed a React application to GitHub pages that uses the Spotify API to get the currently playing track on Spotify and display it in the web page. This runs great locally and used a local server running on port 8888 to send the api requests and redirect to the app running on localhost:3000. The issue is that the app as is gives the above error when I attempt to request the token from the Spotify API and I'm guessing it's because the redirect uri that I'm using is no longer valid since the app is now on GitHub and can't communicate with localhost:3000.
The following is the code that ran on localhost:8888 and retrieved the access token. I changed my redirect URI to match the location of the hosted version of the app.
var express = require('express'); // Express web server framework
var request = require('request'); // "Request" library
var querystring = require('querystring');
var cookieParser = require('cookie-parser');
var client_id = '[MY_CLIENT_ID]'; // Your client id
var client_secret = '[MY_CLIENT_SECRET]'; // Your secret
var redirect_uri = 'http://malcolmross19.github.io/project/'; // Your redirect uri
/**
* Generates a random string containing numbers and letters
* #param {number} length The length of the string
* #return {string} The generated string
*/
var generateRandomString = function(length) {
var text = '';
var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (var i = 0; i < length; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
};
var stateKey = 'spotify_auth_state';
var app = express();
app.use(express.static(__dirname + '/public'))
.use(cookieParser());
app.get('/login', function(req, res) {
var state = generateRandomString(16);
res.cookie(stateKey, state);
// your application requests authorization
var scope = 'user-read-private user-read-email user-read-playback-state';
res.redirect('https://accounts.spotify.com/authorize?' +
querystring.stringify({
response_type: 'code',
client_id: client_id,
scope: scope,
redirect_uri: redirect_uri,
state: state
}));
});
app.get('/callback', function(req, res) {
// your application requests refresh and access tokens
// after checking the state parameter
var code = req.query.code || null;
var state = req.query.state || null;
var storedState = req.cookies ? req.cookies[stateKey] : null;
if (state === null || state !== storedState) {
res.redirect('/#' +
querystring.stringify({
error: 'state_mismatch'
}));
} else {
res.clearCookie(stateKey);
var authOptions = {
url: 'https://accounts.spotify.com/api/token',
form: {
code: code,
redirect_uri: redirect_uri,
grant_type: 'authorization_code'
},
headers: {
'Authorization': 'Basic ' + (new Buffer(client_id + ':' + client_secret).toString('base64'))
},
json: true
};
request.post(authOptions, function(error, response, body) {
if (!error && response.statusCode === 200) {
var access_token = body.access_token,
refresh_token = body.refresh_token;
var options = {
url: 'https://api.spotify.com/v1/me',
headers: { 'Authorization': 'Bearer ' + access_token },
json: true
};
// use the access token to access the Spotify Web API
request.get(options, function(error, response, body) {
console.log(body);
});
// we can also pass the token to the browser to make requests from there
res.redirect('http://malcolmross19.github.io/project/#' +
querystring.stringify({
access_token: access_token,
refresh_token: refresh_token
}));
} else {
res.redirect('/#' +
querystring.stringify({
error: 'invalid_token'
}));
}
});
}
});
app.get('/refresh_token', function(req, res) {
// requesting access token from refresh token
var refresh_token = req.query.refresh_token;
var authOptions = {
url: 'https://accounts.spotify.com/api/token',
headers: { 'Authorization': 'Basic ' + (new Buffer(client_id + ':' + client_secret).toString('base64')) },
form: {
grant_type: 'refresh_token',
refresh_token: refresh_token
},
json: true
};
request.post(authOptions, function(error, response, body) {
if (!error && response.statusCode === 200) {
var access_token = body.access_token;
res.send({
'access_token': access_token
});
}
});
});
console.log('Listening on 8888');
app.listen(8888);
The following is the code for the app itself. I'm sure I need to change the href attribute in the link to go somewhere other than localhost:8888 but I'm not sure exactly where.
import React, { Component } from 'react';
import './App.css';
import SpotifyWebApi from 'spotify-web-api-js';
import ReactCountdownClock from 'react-countdown-clock';
const spotifyApi = new SpotifyWebApi();
class App extends Component {
constructor(){
super();
const params = this.getHashParams();
const token = params.access_token;
if (token) {
spotifyApi.setAccessToken(token);
}
this.state = {
loggedIn: token ? true : false,
nowPlaying: { name: 'Not Checked', albumArt: ''},
currentCount: 5,
clockHidden: true,
songInfoHidden: true,
answer: ''
}
this.getNowPlaying = this.getNowPlaying.bind(this);
this.getHashParams = this.getHashParams.bind(this);
this.toggleClock = this.toggleClock.bind(this);
this.toggleInfo = this.toggleInfo.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
getNowPlaying(){
spotifyApi.getMyCurrentPlaybackState()
.then((response) => {
console.log(response)
this.setState({
nowPlaying: {
name: response.item.name,
albumArt: response.item.album.images[0].url
}
});
})
}
getHashParams(){
var hashParams = {};
var e, r = /([^&;=]+)=?([^&;]*)/g,
q = window.location.hash.substring(1);
e = r.exec(q)
while (e) {
hashParams[e[1]] = decodeURIComponent(e[2]);
e = r.exec(q);
}
return hashParams;
}
toggleClock(){
this.setState({
clockHidden: !this.state.clockHidden
});
}
toggleInfo(){
this.toggleClock();
this.setState({
songInfoHidden: !this.state.songInfoHidden,
});
}
buttonClick(e){
this.getNowPlaying();
if(this.state.songInfoHidden){
this.toggleClock();
} else {
this.toggleInfo();
this.setState({
answer: ''
});
}
}
handleChange(event){
this.setState({
answer: event.target.value
});
}
handleSubmit(event){
event.preventDefault();
this.toggleInfo();
}
render() {
return (
<div className="App">
<div className="MainHeader">
{!this.state.loggedIn &&
<div>
<h1>Login to Spotify to use Name That Tune</h1>
</div>}
{this.state.loggedIn &&
<div>
<h1>Name That Tune</h1>
</div>}
Request Access From Spotify
</div>
<div className="Body">
<hr />
{this.state.loggedIn && !this.state.clockHidden &&
<ReactCountdownClock
seconds={30}
color='#5CC8FF'
alpha={0.9}
size={720}
onComplete={() => this.toggleInfo()}
/>}
{this.state.loggedIn && !this.state.songInfoHidden &&
<div>
<h1 className="Header">How Did You Do?</h1>
<div className="NowPlaying">Now Playing: { this.state.nowPlaying.name }</div>
</div>}
<br />
{this.state.loggedIn && !this.state.songInfoHidden &&
<div>
<img src={this.state.nowPlaying.albumArt} style={{ height: 650 }}/>
</div>}
{this.state.loggedIn && !this.state.clockHidden &&
<form onSubmit={this.handleSubmit}>
<label>
<input type="text" value={this.state.answer} placeholder="Enter Your Answer Here" onChange={this.handleChange} />
</label>
<input type="submit" value="Check Answer" />
</form>}
<br />
{this.state.loggedIn && !this.state.songInfoHidden &&
<div>
<h1>Your Answer:<br /></h1>
<div className="Answer">{this.state.answer}</div>
</div>}
<br />
{this.state.loggedIn &&
<button className="NowPlayingButton" onClick={() => this.buttonClick()}>
Check Now Playing
</button>
}
</div>
</div>
);
}
}
export default App;

Meteor - callback executing twice

I have this Meteor app that sends data to an api then uses the data sent back in the website. However, when I call the function that gets the api data, uploadToCloudinary() which has a callback, I find it running twice. One of the documents get inserted correctly with the correct information and one is missing the res.data.secure_url. Am I not doing the callback thing right or is it because it is non-blocking code, so I think(correct me if I am wrong) that when the imageURL.push function executes, it cannot find a res so it goes and does the other code first and then when it finds the res it pushes it and creates another document.
import { Meteor } from "meteor/meteor"
import React from "react";
import { withRouter, Link } from "react-router-dom";
import SimpleSchema from "simpl-schema";
import axios from "axios"
import { SubjectRoutes } from "./subjectRoutes/subjectRoutes";
import "../methods/methods";
import Menu from "./subComponents/Menu";
class AddNote extends React.Component{
constructor(props){
super(props);
this.state = {
message: "",
loginMessage: (<div></div>),
urls: []
};
}
renderSubjects(subjects){
return subjects.map((item) => {
return <option key={item}>{item}</option>
})
}
componentWillMount() {
Meteor.subscribe('user');
}
addNote(e){
e.preventDefault();
let title = this.refs.title.value;
let subject = this.refs.subject.value;
let description = this.refs.description.value;
let allUrls = [this.refs.imageURL.value].concat(this.state.urls);
let imageURL = allUrls.filter(function(entry) { return entry.trim() != ''; });
let userId = Meteor.userId();
let userEmail = Meteor.user().emails[0].address;
let createdAt = Date.parse(new Date());
let unit = this.refs.unit.value;
let file = this.refs.fileInput.files[0];
if(!Meteor.userId()){
this.setState({
message: "You need to login before you can add a note",
loginMessage: <Link to="/login">Login</Link>
})
throw new Meteor.Error(400, "User is not signed in.")
}
if(title && subject && description && unit){
if(imageURL.length == 0 && file == undefined){
this.setState({ message: "You need to enter an image." })
return;
}
console.log(imageURL.length, file)
if(imageURL){
let noteInfo = { title, subject, description, imageURL, userId, userEmail, createdAt, unit };
Meteor.call("notes.insert", noteInfo, (err, res) => {
if(err){
this.setState({ message: "Please enter a valid image URL." });
}else{
this.props.history.push("/")
}
})
}
if(file){
let noteInfo = { title, subject, description, imageURL, userId, userEmail, createdAt, unit };
this.uploadToCloudinary(file, (err, res) => {
imageURL.push(res.data.secure_url);
Meteor.call("notes.insert", noteInfo, (err, res) => {
//problem .......inserting 2 docs, one empty and one with proper data
console.log("CALLED")
if(err){
this.setState({message: err.reason});
console.log(err);
}else{
this.props.history.push("/")
}
})
});
}
}
}
addLink(){
let file = this.refs.fileInput.files[0];
if(this.refs.imageURL.value || file != undefined){
if(this.state.urls.length < 10){
if(!this.state.urls.includes(this.refs.imageURL.value)){
const URLSchema = new SimpleSchema({
imageURL:{
type:String,
label:"Your image URL",
regEx: SimpleSchema.RegEx.Url
}
}).validate({ imageURL:this.refs.imageURL.value })
let urls = this.state.urls.concat([this.refs.imageURL.value]);
this.setState({ urls });
this.refs.imageURL.value == "";
}else{
this.setState({ message: "You already inserted this note." })
}
}else{
this.setState({ message: "Only allowed 10 notes per upload. "})
}
}else{
this.setState({ message: "Please enter a note." })
}
}
uploadToCloudinary(file, callback){
const CLOUDINARY_URL = "MY_CLOUDINARY_URL";
const CLOUDIARY_UPLOAD_PRESET = "MY_CLOUDIARY_UPLOAD_PRESET"
let formData = new FormData();
formData.append("file", file);
formData.append("upload_preset", CLOUDIARY_UPLOAD_PRESET)
axios({
url: CLOUDINARY_URL,
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
data: formData
}).then(function(res){
callback(new Meteor.Error(400, "Error, cannot connect to cloudinary."), res);
}).catch(function(err){
console.log(err);
})
console.log(file);
}
render(){
return(
<div>
<form onSubmit={this.addNote.bind(this)}>
<Menu />
<p>*Just a friendly reminder: If you cannot read the note yourself,
others cannot as well. Please make sure your notes are clear and
easy to read.*</p>
<h1>Add a note</h1>
<br />
<input className="addNote-input" id="title" ref="title" type="text" placeholder="Title" autoComplete="off" />
<br />
<select ref="subject">
<option selected disabled value="">Choose a subject</option>
{this.renderSubjects(SubjectRoutes)}
</select>
<br />
<input className="addNote-input" id="description" ref="description" placeholder="Description Here..." autoComplete="off" />
<br />
<Link to="/questions">What is this?</Link><br />
<div className="inline full">
<div className="left">
<input id="imageUrl" className="addNote-input insert-link" ref="imageURL" placeholder="Enter image URL here" autoComplete="off" />
</div>
or
<div className="right">
<input className="addNote-input inline" type="file" ref="fileInput" onChange={this.readImage} id="fileInput" autoComplete="off"/>
</div>
<div className="full inline-block">
<span onClick={this.addLink.bind(this)} id="addLink">+</span>
<span>({this.state.urls.length})</span>
</div>
</div>
<input className="addNote-input" placeholder="Subject Unit" type="text" ref="unit" autocomplete="off" />
<br />
<button>Add Note</button>
<br />
<div className="alert alert-danger">Error: {this.state.message}</div>
<br />
{this.state.loginMessage}
</form>
</div>
)
}
}
export default withRouter(AddNote);
PS the function uploadToCloudinary() just receives data as an argument and sends it to an api then puts it into a callback to return an object. And also the console.log("CALLED") is only executed once which is really confusing to me since it is creating two documents so it should be running twice. Thanks in advance!
You're calling notes.insert method twice in addNote():
In if (imageURL) { ... }
In if (file) { ... } — this one is calling uploadToCloudinary first and adds secure_url into imageURL.

Resources