Which HTTP Method to Use with Forms? - reactjs

My web app information:
Frontend: React.js
Backend: Flask
Goal: User enters in zip codes and the backend returns the zip code coordinates and distance on the web page
Problem: I was initially under the impression that you only use POST requests when inserting into a database, so I was defining my API endpoints as GET methods. However, since I am under the assumption that it is best to not pass body parameters for GET methods, my current solution is to have it as a POST request. Is this the best practice? Or is there a better way to do it?
Frontend Code
const handleSubmit = e => {
const formData = { "origin": originInput, "destination": destinationInput
}
e.preventDefault();
checkError();
fetch('/path', {
credentials: 'include',
method: 'POST',
body: JSON.stringify(formData),
headers: { 'content-type': 'application/json' }
}).then(res => console.log(res))
.catch(err => console.log(err));
};
return (
<div className="card">
<form onSubmit={handleSubmit}>
<section>
<label htmlFor="origin">Start Zip</label>
<input
type="text"
name="origin"
id="origin"
onChange={e => handleChange(e, updateOriginInput)}
/>
</section>
<section>
<label htmlFor="destination">End Zip</label>
<input
type="text"
name="destination"
id="destination"
onChange={e => handleChange(e, updateDestinationInput)}
/>
</section>
<section>
<input type="submit" value="Get Directions" />
</section>
</form>
</div>
);
Backend Code
#path.route('/path', methods=['POST'])
def path_info():
request_data = request.get_json()
ORIGIN = request_data['origin']
DESTINATION = request_data['destination']
# The df var is pd.DataFrame(zip([ORIGIN, get_lon(ORIGIN), get_lat(ORIGIN)], [DESTINATION, get_lon(DESTINATION), get_lat(DESTINATION)])).T
df, MILES = preprocess(ORIGIN, DESTINATION)
print(df)
return {
'origin': {
'longitude': df['LON'][0],
'latitude': df['LAT'][0]
},
'destination': {
'longitude': df['LON'][1],
'latitude': df['LAT'][1]
},
'miles': MILES
}
Thanks in advance!

I was initially under the impression that you only use POST requests when inserting into a database, so I was defining my API endpoints as GET methods. However, since I am under the assumption that it is best to not pass body parameters for GET methods, my current solution is to have it as a POST request. Is this the best practice? Or is there a better way to do it?
Today: if you need to send information in the body of the HTTP request, then you should expect to use POST (with PUT/PATCH available in specific cases).
If you want to instead use GET, then you need to take the information collected by the form and figure out how to encode it into the request URI itself. On the web, that usually takes the form of key value pairs that have been application/x-www-form-urlcoded and copied into the query part of the request URI. It doesn't have to be in the query part - URI Templates can be used to describe the expansion of URI paths as well.
There is an internet draft to standardize a new HTTP method (QUERY) which would provide semantics for a safe request with a message body; but that method hasn't been standardized or registered yet.

Related

IPFS Infura ERROR Failed to load resource: the server responded with a status of 401 (Unauthorized) (POST REQUEST)

I am currently building an NFT-related app and currently trying to mint the NFT using a test network. I am trying to upload the image file to ipfs.
To upload an image, I am currently using a dropzone to get the file:
const client = ipfsHttpClient('https://ipfs.infura.io:5001/api/v0');
const CreateItem = () => {
const uploadToInfura = async (file) => {
try {
const added = await client.add({ content: file });
const url = `https://ipfs.infura.io/ipfs/${added.path}`;
setFileUrl(url);
} catch (error) {
console.log('Error uploading file: ', error);
}
};
const onDrop = useCallback(async (acceptedFile) => {
await uploadToInfura(acceptedFile[0]);
}, []);
const {
getRootProps,
getInputProps,
isDragActive,
isDragAccept,
isDragReject,
} = useDropzone({
onDrop,
accept: 'image/*',
maxSize: 5000000,
});
const fileStyle = useMemo(
() => `dark:bg-nft-black-1 bg-white border dark:border-white border-nft-gray-2 flex flex-col items-center p-5 rounded-sm border-dashed
${isDragActive ? ' border-file-active ' : ''}
${isDragAccept ? ' border-file-accept ' : ''}
${isDragReject ? ' border-file-reject ' : ''}`,
[isDragActive, isDragReject, isDragAccept],
);
return (
<div className="flex justify-center sm:px-4 p-12">
<div className="w-3/5 md:w-full">
<h1 className="font-grotesque text-white text-2xl">
Create new item
</h1>
<div className="mt-16">
<p className="font-grotesque dark:text-white text-nft-black-1 text-xl">
Upload file
</p>
<div className="mt-4">
<div {...getRootProps()} className={fileStyle}>
<input {...getInputProps()} />
<div className="flexCenter flex-col text-center">
<p className="font-grotesk dark:text-white text-nft-black-1 text-xl">
JPG, PNG, GIF, SVG, WEBM, MP3, MP4. Max 100mb.
</p>
<p className="font-poppins dark:text-white text-nft-black-1 font-grotesk text-sm">
Drag and Drop File
</p>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default CreateItem;
Above is a simplified version of my component. I am attempting to pass the file as a prop to my own upload to infura method.
The program was working fine however when attempting to use the platform today I kept running into the same error.
Whenever an attempt to upload was made the POST request fails. This is the error logged:
ipfs.infura.io:5001/api/v0/add?stream-channels=true&progress=false:1 Failed to load resource: the server responded with a status of 401 (Unauthorized)
fetch.browser.js?c17b:106 POST https://ipfs.infura.io:5001/api/v0/add?stream-channels=true&progress=false 401 (Unauthorized)
I am connected to wifi and within the file size limit. Is anyone familiar with this error?
I think you have to pass the authorization header when you make request to infura. before it was easy to use infura but now they even require credit card to hold on to the account.
The Infura IPFS public gateway was deprecated on August 10, 2022
You probably get this error, too "HTTPError: project id required"
const ipfsClient = require(‘ipfs-http-client’);
const projectId = ’XXX...XXX;
const projectSecret = ‘XXX...XXX’;
const auth = ‘Basic ’ + Buffer.from(projectId + ‘:’ + projectSecret).toString(‘base64’);
const client = ipfsClient.create({
host: ‘ipfs.infura.io’,
port: 5001,
protocol: ‘https’,
headers: {
authorization: auth,
},
});
Read this to enable a dedicated gateway: ipfs-public-api-and-gateway-deprecation
You can now access a dedicated gateway to decentralized storage for
your projects through the Infura IPFS API. A gateway allows
applications, such as browsers, that do not natively support IPFS to
access IPFS content. This is a new feature our team has released today
in anticipation of our gradual deprecation of our public gateways in
October.
Now you are able to create your own unique gateways, dedicated to only
your projects. With this initial release, you can also create your own
subdomain and restrict your view to show only content you have pinned.
These features were previously not available when you accessed IPFS
through the public gateway.
As an alternative to Infura, you could use Web3 Storage for IPFS. You don't have to put a credit card down and initial setup is easier than Infura IMO. I'm also assuming your following the JSMastery NFT course. So once you get an API key your client would look like:
const client = new Web3Storage({ token: process.env.WEB_3_STORAGE_KEY });
Then the uploadToIPFS method would be:
const uploadToIPFS = async (files, setFileURL) => {
const cid = await client.put(files);
const url = `https://${cid}.ipfs.w3s.link/${files[0].name}`;
console.log('stored files with cid:', cid);
console.log(url);
return url;
};
In createNFT, storing data such as name, description, and imageURL wont be part of the infura "update" function anymore though, so you'd need to create a blob of that data + the link you just created to store the NFT. So within the createNFT function you'll need to adjust the data variable to something like:
const data = new Blob([JSON.stringify({ name, description, image: fileURL, fileID })], { type: 'application/json' });
There's a few other adjustments you'll have to make to your code such as changing trusted NextJS Image sources, and how you link your blob to the image with a unique value but if you go through the Web3 Storage docs you should be able to figure it out.
I kept running into problems with Infura and this works much better now. Feel free to take a look at my code on github. It's part of it's own branch so you can see everything that has the comment "Changed IPFS Provider" to see what was changed to accommodate the switch.
I also encountered a similar issue. I think the previous infura ipfs link is deprecated due to certain updates made by infura recently. You'd need to create a dedicated gateway to resolve this issue. Hope this helps.

JavaScript CORS error when uploading files with Axios

I am developing a web application with Flask on the backend and React and Redux on the frontend.
I want to add a "Change Profile Picture" option to the profile page but whenever I make a post request with axios to my /api/user/upload_image/ route, i get the following errors:
Access to XMLHttpRequest at 'http://localhost:5000/api/user/update_image' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
PATCH http://localhost:5000/api/user/update_image net::ERR_FAILED
Which is weird becuase I have set up my CORS wrapper in my Flask app like so:
self.cors = CORS(self.app, resources={r"/api/*": {"origins": "*"}})
which should allow requests to /api/ from all origins.
I also tried to do the same thing with Postman and it worked like a charm - uploaded the file and saved it to /server/public/profile_pictures/
When i try to upload regular JSON text from my react application it works as well. It bugs out on file uploads only.
Here is the JSX for the input + the event handler
<label>
Change Profile Picture
<input onChange={(e) => {
this.setState({image: e.target.files[0]})}
} type="file" name="image" />
</label>
Then i have a submit button which dispatches the following action with this.state.image as a parameter:
export const updateImage = (file) => {
return async (dispatch, getState) => {
const formData = {
user_id: getState().currentUser.user.user_id,
auth_key: getState().currentUser.auth_key,
image: file
}
Axios.patch("http://localhost:5000/api/user/update_image", formData, {
headers: {
'Content-Type' : 'multipart/form-data'
}
})
.then(response => {
dispatch({type: UPDATE_IMAGE, payload: response.data})
})
}
I tried using the built in formData method to create the JS object too but that was no good either.
Finally here is the python method which is called when the /api/user/update_image route is hit:
def update_image(self, request):
image = request.files['image']
data = request.params
image.save("./public/profile_pictures/user_p_picture_id_"+data['user_id']+".jpg")
fsql.update("""UPDATE users SET profile_picture = %s WHERE user_id = %s""", ("/public/profile_pictures/user_p_picture_id_"+data['user_id']+".jpg", data['user_id']))
return jsonify({
"error_code" : "200",
"error_message" : "Success"
})
I actually solved this about a week and a half ago but I checked the status today.
So the solution was to make a few changes to my config parameter and CORS parameters. Here is the configs i am using right now:
config = {
'ORIGINS': [
'http://localhost:3000', # React
'http://127.0.0.1:3000', # React
],
'SECRET_KEY': '...' #secret key
self.cors = CORS(self.app, resources={
r'/api/*': {
"Access-Control-Allow-Origin": config["ORIGINS"],
"Access-Control-Allow-Credentials": True,
'supports_credentials': True
},
},
supports_credentials = True,
expose_headers = "*"
)
self.app.config['UPLOAD_FOLDER'] = r'/*' # Change this to only the folder you want to save images to
self.app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # Change this according to your file size
This solved my CORS and file transport issues.
I really hope this helps someone. The CORS docs on flask-cors do not cover everything in regards to file uploading and session storage so we kind of have to solve the errors without knowing how everything works - like trying to solve a puzzle with missing pieces.
HMU in messages if you have any good tools for CORS in flask which are well documented and have a community around them.

Unable to send attachment with the email using reactjs

I'm new to react, my objective is to send attachment with the email. For emailing purpose, we're using sendgrid and for uploading files or attachments we're sendgrid mail helper attachment in backend. But i couldn't able to upload file, i can send only email but i couldn't able to send email with attachment. I couldn't able to figure it out where i'm going wrong. I have been struggling from couple of weeks but couldn't figure out where i'm going wrong.
Here is the code:
fileChange = e => {
this.setState(
{ file: e.target.files[0], fileName: e.target.files[0].name },
() => {
console.log(
"File chosen --->",
this.state.file,
console.log("File name --->", this.state.fileName)
);
}
);
};
<Button.Content hidden>Choose a File</Button.Content>
</Button>
<input
type="file"
id="file"
hidden
onChange={this.fileChange}
/>
<Form.Input
fluid
label="File Chosen: "
placeholder="browse your file system"
readOnly
value={this.state.fileName}
/>
Can anyone help me in this query please?
you are passing file but you set wrong content-type.
headers: { 'Content-Type': 'application/json' }
Please use the following line instead above line
headers: {"Content-Type": "multipart/form-data"}

How to Save Image in server

I am trying to save an image in database but when I do the following it saves with dataURL, but that URL starts with localhost, how can I prevent it? I used React for frontend.
uploadImage = (event) => {
var imgSize = event.target.files[0].size;
$('#img').attr('hidden', false);
if (imgSize < 1000000 ) {
this.setState({
image: URL.createObjectURL(event.target.files[0]),
imageSize: imgSize
});
document.getElementById("errImgUpload").innerHTML = "";
}
else {
document.getElementById("errImgUpload").innerHTML = "Maximum image size 1Mb";
}
}
<div className="form-group">
<label for="file-upload" className="custom-file-upload">
<span className="fa fa-upload"></span> Upload image
</label>
<input onChange={(event) => this.uploadImage(event)} name="file-upload" id="file-upload" type="file" accept="image/*" />
<span id="errImgUpload" className="text text-danger"></span>
</div>
The Blob is http://localhost:10002/b46e96f5-83ce-4d10-b668-2bd038721b5a, what is a blob?
URL.createObjectURL() creates a blob which is a binary representation of the file in the memory. It doesn't upload the file. I am not sure from where you got the code. You may want to read more about this at MDN.
Uploading requires a backend service to open an endpoint for you to send post data to. You need to use <input type='file'> tag to send the file as form data and set the service endpoint as the url in the form. Start with this article.
You need to post your image data with FormData, like this:
const formData = new FormData();
formData.append('image', files[0]);
fetch(url, {
method: 'POST',
body: data
}).then(...);
And blob is Binary Large Object, more detail you can find in MDN.

Bad request status 400. Required String parameter 'login' is not present

I am trying to build login with react.js and connect it to my springboot.
Here is my code, react.js:
import React from 'react';
export default class Login extends React.Component {
constructor() {
super();
this.state = {
login:"",
password:""
}
}
// This will be called when the user clicks on the login button
login(e) {
e.preventDefault();
console.log(this.state.password)
function createCORSRequest(method, url) {
var xhr = new XMLHttpRequest();
if ("withCredentials" in xhr) {
// Check if the XMLHttpRequest object has a "withCredentials" property.
// "withCredentials" only exists on XMLHTTPRequest2 objects.
xhr.open(method, url, true);
} else if (typeof XDomainRequest != "undefined") {
// Otherwise, check if XDomainRequest.
// XDomainRequest only exists in IE, and is IE's way of making CORS requests.
xhr = new XDomainRequest();
xhr.open(method, url);
} else {
// Otherwise, CORS is not supported by the browser.
xhr = null;
}
return xhr;
}
var xhr = createCORSRequest('POST', "http://localhost:8080/test/login");
if (!xhr) {
throw new Error('CORS not supported');
}
fetch().then(r => r.json())
.then(data => console.log(data))
.catch(e => console.log(e))
}
render() {
return (
<form role="form">
<div>
<input type="text" name="login" placeholder="Username" />
<input type="password" name="password" placeholder="Password" />
</div>
<button type="submit"onClick={this.login.bind(this)}>Login</button>
</form>
);
}
}
And this is my springboot code that is located as TestController:
#RestController
public class TestController {
#RequestMapping(value = "/test/login", method = RequestMethod.GET )
public Boolean testLogin(#RequestParam String login, #RequestParam String password) {
if ( login.equals ("ajt"))
return true;
else {
return false;
}
}
Each of them are present in two different ports, react on :9000 and springboot on :8080.
Also, on my react page I get the error:
TypeError: Failed to execute 'fetch' on 'Window': 1 argument required, but only 0 present.(…)
Any ideas?
for info: I have only got 6 months coding behind me -_- please be kind!
There's a handful of small mistakes here. I'll try to point you in the right direction for a few of them.
First of all, I just wouldn't use fetch. It's listed as an expiremntal technology by MDN, and it's browser support is weak. For someone just starting out with web development, you're much better off using a more established and "safe" technology. Either simple XMLHTTP ajax or using the ajax method from jquery. You already seem to be going down the path of XMLHTTP, so I would suggest just replacing your fetch commands with the example I linked above.
Second, you're using two different HTTP methods. Your ajax object is going to send a POST command, but your server is listening for a GET command. This is set up on the following lines;
var xhr = createCORSRequest('POST', "http://localhost:8080/test/login");
#RequestMapping(value = "/test/login", method = RequestMethod.GET )
If you want those two pieces of code to talk to one another, they need to be set to the same method. In this case, you want a POST for both, but it's worth learning the distinction between the two for the future.
Lastly, there's the issue of getting information from your inputs and into your ajax XMLHTTP object. To do this, you're going to want to set up onChange hooks on the inputs, and attach them to a handleChange function within the react component. Use this function to save the values to the component's state, and then later take the values out of state to apply to the xmlhttp.
A simple example of what I am describing;
render() {
return (
<div>
<input type="text" name="login" onChange={this.handleChange}/>
<button onClick={this.login.bind(this)}>Login</button>
</div>
);
}
handleChange(value) {
this.setState({login: value});
}
login () {
var xmlhttp = new XMLHttpRequest(); // new HttpRequest instance
xmlhttp.open("POST", "/test/login");
xmlhttp.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xmlhttp.send(JSON.stringify({login: this.state.login}));
}

Resources