invalid JSON error when clicking button in reactjs - reactjs

I am trying to build a simple react page where there are two buttons at the beginning.
The first sends the string 'private' as response when clicked, the second sends 'public' as a response when clicked.
The two strings should come from a mongodb database. It's not done yet, because whenever I click on any of the buttons, I get the same error over and over again.
It's the following:
Uncaught (in promise) SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
This project has a backend server with two GET requests and a react frontend.
You can see them below.
The frontend:
function PubPrivBtn() {
async function clickHandler(e) {
if (e.target.id === 'pubBtn') {
let response = await fetch('/api/public');
let result = await response.json();
console.log(result);
}
if (e.target.id === 'privBtn') {
let response = await fetch('/api/private');
let result = await response.json();
console.log(result)
}
}
return (
<div>
<button onClick={(e) => clickHandler(e)} id='pubBtn'>Public</button>
<button onClick={(e) => clickHandler(e)} id='privBtn'>Private</button>
</div>
)}export default PubPrivBtn;
And the backend:
import mongoose from "mongoose";
import express from "express";
import props from './porp.model.js'
const app = express();
app.use(express.json());
mongoose.connect('mongodb://localhost/auth', async (err, db) => {
if (err) {
console.log(err)
}
else{
console.log('Connected!')
}
app.get('/api/public', async (req, res) => {
let pub = await props.where("vis").equals("public")
console.log(pub);
res.send(pub)
})
app.get('/api/private', async (req, res) => {
let priv = await props.where("vis").equals("private")
console.log(priv);
res.send(priv)
})
})
app.listen(3001)
Plus the schema is const porp = new mongoose.Schema({ vis: String }).
I have tried to remake the whole project from zero (not like it was a big deal), but nothing changed, so I have to think the problem is not in my code. Or maybe I'm wrong and it's something obvious.

My "proxy": "http://localhost:3001", was in the wrong package.json. (the backend's)
I have replaced it to the frontend and it's now working perfectly fine.
Thanks #Konrad Linkowski! If I hadn't noticed it, your comment would have solved it for sure!

Related

Uploading React - Node image(s) to Cloudinary

Has anyone uploaded an image using Node, React, Antd library to cloudinary? I'm getting an error saying that the file is missing. I'm not sure if I should be sending the entire file object or just the name. I have sent the thumburl (sometimes it works others it doesn't there has to be something I'm doing wrong).
This is my backend
const uploadImage = async (req, res) => {
try {
const result = await cloudinary.uploader.upload(req.body.image, {
public_id: `${Date.now()}`,
resource_type: 'auto' //jpeg or png
});
res.json({
public_id: result.public_id,
url: result.secure_url
});
} catch (error) {
console.log(error);
res.status(400).send(error)
}}
This is my frontend:
const Uploader = () => {
const { token } = useSelector(state => state.user);
const handleChange = ({file}) => {
console.log(file)
axios.post(`${process.env.REACT_APP_API}/uploadImages`, {file}, {
headers:{
authtoken: token
}
})
.then(res => console.log(res))
.catch(err => console.log(err))
};
return (
<Upload
listType="picture"
showUploadList
onChange={handleChange}
>
<Button>Upload</Button>
</Upload>
)
}
export default Uploader
IDK why the onChange triggers 3 times when it has worked I've sent the thumbUrl and it uploads 3 times, I have seen that I can use beforeUpload but I believe this works before uploading. I want to upload, preview the image and sending it to the server and then send it to my Form Component to add it to the values I have there
Anyone who has already uploaded could help or any ideas would be appreciated?
When it comes to the file not uploading properly, I am guessing it is because the res.body.image isn't the actual file. I would look at what the value is there. I would guess you are missing a middleware.
As far as your frontend issue, I'm still a little unclear about what the issue you are having exactly is. For an example of a clean frontend upload I would check out https://codesandbox.io/embed/jq4wl1xjv. You could also consider using the upload widget which will handle the preview as well as give you some easy editing options.
https://cloudinary.com/documentation/upload_widget
I was able to figure it out, indeed there is a need of a middleware, I used formidable on the routes of my backend
router.post('/uploadImages', authCheck, adminCheck, formidable({maxFileSize: 5 * 1024 * 1024}), imageUpload);
And fixed the controller
const imageUpload = async (req, res) =>{
try {
//console.log('req files:', req.files.file.path)
const result = await cloudinary.uploader.upload(req.files.file.path, {
public_id: `${Date.now()}`,
resource_type: 'auto'
});
res.json({
public_id: result.public_id,
url: result.secure_url
})
} catch (error) {
console.log(error)
}
}
As far as the Frontend goes. Ant designs component takes an action that makes the POST to the backend, and it also takes headers if needed (in my case it takes an authtoken for firebase).
When the image is uploaded it makes the POST to the backend based on the "action". The response will bring back the "FileList" with the upload URL to access it.
<Upload
listType="picture"
showUploadList
multiple
action={`${process.env.REACT_APP_API}/uploadImages`}
headers={{authtoken: token}}
>
<Button>Upload Images</Button>
</Upload>
I hope this helps somebody else too
I tried this and it's worked for me.
Here I used Multer middleware for handling form data(for uploading files).
I used this route,
router.post('/uploadImages', authMiddleware, multer.single("image"), imageUpload);
Corrected controller,
try {
let result;
if (req.file) {
result = await cloudinary.uploader.upload(req.files.file.path, {
public_id: `${Date.now()}`,
resource_type: 'auto'
});
}
res.json({
public_id: result.public_id,
url: result.secure_url
})
} catch (error) {
console.log(error)
}
}
//Multer config file,
const multer = require("multer");
const path = require("path");
module.exports = multer({
storage: multer.diskStorage({}),
fileFilter: (req, file, cb) => {
let ext = path.extname(file.originalname);
if (
ext !== ".jpg" &&
ext !== ".jpeg" &&
ext !== ".png" &&
ext !== ".PNG" &&
ext !== ".JPG" &&
ext !== ".JPEG"
) {
cb(new Error("File type is not supported"), false);
return;
}
cb(null, true);
},
});

How do I get Axios to hit routes in monorepo?

I am using a monorepo-styled build and I am struggling to get routes to hit properly. I am clicking my button:
const handleSubmit = () => {
console.log("button clicked");
axios.get('/', {
params: {
username: "John1904",
}
})
.then((res) => {
console.log(res.data);
});
};
And I am getting the index.ts file, which makes sense. When I attempt to use '/users or /users/, it doesn't work. I get
>GET http://localhost:3000/users?username=John1904 404 (Not Found)
>Uncaught (in promise) AxiosError {message: 'Request failed with status code 404'
This is in the browser console, while nothing else shows in my terminal.
My index.ts that is handling routes is:
const app: Express = express();
const port = process.env.PORT;
app.use(helmet());
app.use(cors());
app.use(express.json());
app.use('/users', usersRouter);
app.use('/', (_req, res) => res.status(200).send('Service online'));
app.use(errorHandler);
app.use(notFoundHandler);
And my server/src/routes/users/router.ts file is:
const router = express.Router();
router.get('/', async (_req: Request, res: Response) => {
try {
const items = await UserService.findAll();
res.status(200).send(items);
} catch (e) {
res.status(500).send(unwrapRouterErrorMessage(e));
}
});
So why is my request not going into the app.use('/users', usersRouter);?
Okay, thanks for asking about the ports, because I checked the URL requests in the dev tools and it was all going through to the frontend and not hitting the backend. Everything was going to the port 3000 instead of 3006. For now I hardcoded the route to go to http://localhost:3006/ and it worked. Thanks again.

FormData with NextJS API

Background
I am trying to create a simple CRUD application using NextJS along with react-redux, so what it does is that it saves peoples contacts.So when adding a contact i am trying to send some data along with a file to a NextJS API.
Issue
ContactAction.js
Make a POST request from redux action to add a contact
export const addContact = (data) => async (dispatch) => {
try {
var formData=new FormData();
formData.append('name',data.Name);
formData.append('email',data.Email);
formData.append('phone',data.Phone);
formData.append('image',data.Image);
let response= await Axios.post(`http://localhost:3000/api/contact/addContact`,formData,{
headers:{
'x-auth-token':localStorage.getItem('token')
}
});
} catch (error) {
console.log(error);
}
}
addContact.js
This is the API route in /api/contact/
const handler = async (req, res) => {
switch(req.method){
case "POST":{
await addContact(req,res)
}
}
}
const addContact = async (req, res) => {
console.log(req.body);
// do some stuff here and send response
}
this is what i get in the terminal after the log,also the file is Gibberish as well when logging req.files
Current Effort
I tried using third party packages such as formidable and formidable-serverless but got no luck. so after a day i made it work with a package called multiparty.
addContact.js
const handler = async (req, res) => {
switch(req.method){
case "POST":{
let form = new multiparty.Form();
let FormResp= await new Promise((resolve,reject)=>{
form.parse(req,(err,fields,files)=>{
if(err) reject(err)
resolve({fields,files})
});
});
const {fields,files} = FormResp;
req.body=fields;
req.files=files;
await addContact(req,res)
}
}
}
const addContact = async (req, res) => {
console.log(req.body); //Now i get an Object which i can use
// do some stuff here and send response
}
The above solution is obviously redundant and probably not the best way to go about it plus i don't want to add these 7 8 lines into each route.
so if someone could help me understand what i am doing wrong and why formData doesn't seem to work with NextJS API (when it works with the Express server) i would be grateful.
FormData uses multipart/form-data format. That is not a simple POST request with a body. It is generally used for uploading files, that's why it needs special handling. As an alternative, you could use JSON.
Here is my solution, i hope this helps anybody.
First of all you need to install next-connect and multer as your dependencies.
Now you can use this API route code.
import nextConnect from "next-connect";
import multer from "multer";
const apiRoute = nextConnect({
onError(error, req, res) {
res.status(501).json({ error: `Sorry something Happened! ${error.message}` });
},
onNoMatch(req, res) {
res.status(405).json({ error: `Method "${req.method}" Not Allowed` });
},
});
apiRoute.use(multer().any());
apiRoute.post((req, res) => {
console.log(req.files); // Your files here
console.log(req.body); // Your form data here
// Any logic with your data here
res.status(200).json({ data: "success" });
});
export default apiRoute;
export const config = {
api: {
bodyParser: false, // Disallow body parsing, consume as stream
},
};
Here is an example about uploading file with Next.js:
https://codesandbox.io/s/thyb0?file=/pages/api/file.js
The most important code is in pages/api/file.js
import formidable from "formidable";
import fs from "fs";
export const config = {
api: {
bodyParser: false
}
};
const post = async (req, res) => {
const form = new formidable.IncomingForm();
form.parse(req, async function (err, fields, files) {
await saveFile(files.file);
return res.status(201).send("");
});
};
const saveFile = async (file) => {
const data = fs.readFileSync(file.path);
fs.writeFileSync(`./public/${file.name}`, data);
await fs.unlinkSync(file.path);
return;
};
Generally speaking,in your api file,you should disable the default bodyParser,and write your own parser

Login status in React

I created authorization in javascript. Then if success login I redirect to React project with url parameter http://localhost:3000/?phoneNum=%2B77072050399
Then in React I get userId by using the passed url parameter => phoneNumber using axios.
I realized it in App.js. Code below:
let url = window.location.href;
let params = (new URL(url)).searchParams;
const userPhoneNum = encodeURIComponent(params.get('phoneNum'));
const [userToken, setUserToken] = useState(null);
const getUserToken = async() => {
try {
const data = await axios
.get(`https://stormy-escarpment-89406.herokuapp.com/users/getToken?phone_number=${userPhoneNum}`)
.then(response => {
setUserToken(response.data);
})
.catch(function(error) {
console.log('No such user! Error in getting token!');
});
} catch (e) {
console.log(e);
}
}
useEffect(() => {
getUserToken();
console.log(userToken);
}, userToken);
So, when I go to next page localhost:3000/places, it is requesting for userToken again with parameter null, because there is no param phoneNum.
How to make it to request only one time and save the userId after it is taken in main page. So, then only when I click LogOut button reset the variable where userID is saved.
If you want to do that without using any third party libraries you can use browser's in built storage api
So, when you receive the token, you can store that in the local storage of the browser using localstorage.setItem and later when you wan to see if the token is there or not just read from there using localStorage.getItem
const getUserToken = async() => {
try {
const data = await axios
.get(`https://stormy-escarpment-89406.herokuapp.com/users/getToken?phone_number=${userPhoneNum}`)
.then(response => {
setUserToken(response.data);
Storage.setItem('token',JSON.stringify(response.data))
})
.catch(function(error) {
console.log('No such user! Error in getting token!');
});
} catch (e) {
console.log(e);
}
}
For Logout you can simply remove the token using localStorage.removeItem
You can easily achieve this by using the react-cookie library
npm i react-cookie
Can be easily implemented in your code by
cookies.set('key', value, { path: '/' });
cookies.get('key')
After getting the userNumber form the param
const userPhoneNum = encodeURIComponent(params.get('phoneNum'));
cookies.set('userphoneNum', userPhoneNum);
View the documentation for more information
https://www.npmjs.com/package/react-cookie

Multer req.file is always undefined despite file object created

I read all the questions related to this already and nothing works. I spent the entire day on this and I can't figure out what's wrong.
I am trying to upload a file to my database using React, mongoose, mongoDB and express/node.
Here is what I already did:
Added method="post" and enctype="multipart/form-data" to my form
the "name" property of the file input is "icon" and in the middleware upload.single("icon") too.
On submit I have a function action that is called.
export const updateUserIconette = icon => async dispatch => {
console.log("Number 1");
console.log(icon);
try {
const res = await axios.post("/me", icon);
dispatch({
type: UPDATE_PROFILE_SUCCESS,
payload: res.data
});
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach(error => dispatch(setAlert(error.msg, "danger")));
}
dispatch({
type: UPDATE_PROFILE_ERROR
});
}
};
Here, the console.log number 1 logs and the log of icon too, which is a file object.
And the API Post route is here:
const multer = require("multer");
const storage = multer.diskStorage({
destination: function(req, file, callback) {
callback(null, "./uploads/");
},
filename: function(req, file, callback) {
callback(null, file.originalname);
}
});
const fileFilter = (req, file, callback) => {
if (file.mimetype === "image/jpeg" || file.mimetype === "image/png") {
callback(null, true);
} else {
callback(null, false);
}
};
const upload = multer({ storage: storage, fileFilter: fileFilter });
router.post(
"/",
[
auth,
upload.single("icon")
],
async (req, res) => {
console.log("number 2")
console.log(req.file)
// Build the Profile object
const profileFields = {};
profileFields.user = req.user.id;
if (req.body.username) profileFields.username = req.body.username;
if (req.body.bio) profileFields.bio = req.body.bio;
if (req.body.location) profileFields.location = req.body.location;
if (req.file) profileFields.icon = req.file.path;
try {
let user = await User.findOne({ _id: req.user.id });
if (user) {
//Update
user = await User.findOneAndUpdate(
{ _id: req.user.id },
{ $set: profileFields },
{ new: true }
);
return res.json(user);
}
await user.save();
res.json(user);
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
}
);
Here, the console.log number 2 logs but not the req.file because it is empty.
Alright, here's a working demo using react, multer, express, mongodb and mongoose. It's not fancy, pretty much bare bones, but it works. What you should do is step through your project and make sure each file/function/middleware works as intended in the flow (see flow example below).
Github repo: https://github.com/mattcarlotta/upload-image-example
Follow the README for instructions on how to run it.
All important files will have notes as to what their function does.
Files to focus on:
client-side form (specifically sending FormData here)
multer middleware
upload icon route
saveIcon middleware
createIcon controller
Flow:
User adds a file (image) and an email address and clicks Submit to send a POST request to the API.
The POST request first passes through the multer middleware.
If valid, then the request is handled via the upload icon route.
The route passes it to the saveIcon middleware.
The saveIcon middleware saves the image to uploads then passes the request to the createIcon controller.
The controller saves the email and icon filepath to the mongodb database, and then sends back a response (message and remoteicon) to the client-side form.
The client-side form receives a res (response) from the API and sets it to state.
When the state is updated, the remoteicon is set as a src to an img element, which in turn, makes a GET request back to the API, which in turns sends back the uploaded image.
Ideally, most of this flow will be a microservice (and not attached to your main application), as it will vastly simplify your structure. More info can be found here.

Resources