Book info Goodreads API - reactjs

Trying to do a search on the Goodreads API for book information. At the same time I'm trying to convert the data from xml to json. I get this error when I do a search for a book
No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access.
and warning
Cross-Origin Read Blocking (CORB) blocked cross-origin response https://www.goodreads.com/search/index.xml?key=PL6saHk8cIrLeeLG3eylg&q=halo with MIME type application/xml. See https://www.chromestatus.com/feature/5629709824032768 for more details.
I installed xml2js package and placed it into my function like this
searchBooks = async (e) => {
e.preventDefault();
const search = this.state.search;
try {
let res = await axios.get(
`https://www.goodreads.com/search/index.xml?key=PL6saHk8cIrLeeLG3eylg&q=${search}`
);
let xml = res.data;
parseString(xml, (error, res) => {
this.setState({
books: res.data
});
});
} catch (error) {
this.setState({ error });
}
console.log(this.state.books);
};
What do I need to fix?

GoodReads API doesn't let you make an API call from front-end. (Refer to this GoodReads forum question).
You need to make an API request from back-end service (e.g. nodejs server), or go through a proxy.
Setting up a proxy can be a pain, so you can call the API using YQL (Yahoo's Query Language) client.
So here is the workaround.
⚠️ WARNING: This is not a good practice but writing it here for an academic purpose.
Please consider setting up your own back-end service to call the API.
Calling YQL directly can be hairy, so you can use another library called, proxyfy-url, which gives you a proxified YQL URL.
var proxify = require('proxify-url');
...
get proxyfiedUrl() {
// GoodReads API returns result in "XML" format.
// "XML" is the "input" format fed into YQL
let proxyUrl = proxify(this.url, { inputFormat: 'xml' });
return proxyUrl;
}
Shameless plug!
If you want more information, I wrote about the same problem on my blog
How to call GoodReads API using YQL

Related

react-jsonp giving a CORB error in a React App

Edit
based on suggestions tried doing it with fetch, now I am getting this
I am trying to get data from gnews.io/api in a simple react app.I am learning React and the solution might have been to easy but I am stuck here and cant figure out what is wrong and I keep getting this error
fetch-jsonp.js:88 Cross-Origin Read Blocking (CORB) blocked cross-origin response https://gnews.io/api/v4/top-headlines?country=us&token=myapi with MIME type application/json.
funny thing is that if I copy this url https://gnews.io/api/v4/top-headlines?country=us&token=MYAPI&callback=jsonp_1642257010280_37644
and paste it in the browser I am getting the desired response.
Would really appreciate any sort of help
this is the useEffect function that is making this api call
React.useEffect(()=> {
async function getNewsdata(country){
try {
let url = `https://www.gnews.io/api/v4/top-headlines?country=pk&token=${API2}`
let response = await fetchJsonp(url)
let result = await response.json()
console.log("News Data:", result)
} catch (error) {
console.log("error loading news data: ", error)
}
}
getNewsdata(props.country.trim())
},[])
Resorting to JSONP is frowned upon nowadays; there are safer alternatives, such as CORS, for cross-origin communication. As the response's content type is application/json, using JSONP will not work anyway because it causes Chrome's CORB feature to kick in.
Why not try to solve whatever CORS issue you seem to be having? I'd be very surprised if the API you're using weren't configured for CORS... A casual inspection of their documentation reveals that you're using the wrong domain, www.gnews.io, instead of gnews.io. The former redirects to the latter, but is not configured for CORS, which explains your CORS troubles.
Once you use the right domain (gnews.io), all your CORS troubles go away. And because there's no longer any need to reach for dirty tricks like JSONP, you can use good old reliable fetch rather than some third-party tool.
React.useEffect(()=> {
async function getNewsdata(country){
try {
let url = `https://gnews.io/api/v4/top-headlines?country=pk&token=${API2}` // gnews.io, not www.gnews.io
let response = await fetch(url) // fetch, not fetchJsonp
let result = await response.json()
console.log("News Data:", result)
} catch (error) {
console.log("error loading news data: ", error)
}
}
getNewsdata(props.country.trim())
},[])

What's the best way to store a HTTP response in Ionic React?

I'm developing an app with Ionic React, which performs some HTTP requests to an API. The problem is I need to store the response of the request in a local storage so that it is accessible everywhere. The way I'm currently doing it uses #ionic/storage:
let body = {
username: username,
password: password
};
sendRequest('POST', '/login', "userValid", body);
let response = await get("userValid");
if (response.success) {
window.location.href = "/main_tabs";
} else if (!response.success) {
alert("Incorrect password");
}
import { set } from './storage';
// Handles all API requests
export function sendRequest(type: 'GET' | 'POST', route: string, storageKey: string, body?: any) {
let request = new XMLHttpRequest();
let payload = JSON.stringify(body);
let url = `http://localhost:8001${route}`;
request.open(type, url);
request.send(payload);
request.onreadystatechange = () => {
if (request.readyState === 4 && storageKey) {
set(storageKey, request.response);
}
}
}
The problem is that when I get the userValid key the response hasn't come back yet, so even awaiting will return undefined. Because of this I have to send another identical request each time in order for Ionic to read the correct value, which is actually the response from the first request. Is there a correct way of doing this other than just setting timeouts everytime I perform a request?
You are checking for the results of storage before it was set. This is because your sendRequest method is calling an asynchronous XMLHttpRequest request, and you are checking storage before the sendRequest method is complete. This can be fixed by making sendRequest async and restructuring your code a bit.
I would suggest you instead look for examples of ionic react using hooks or an API library - like fetch or Axios. This will make your life much easier, and you should find lots of examples and documentation. Check out some references below to get started:
Example from the Ionic Blog using Hooks
Example using Fetch using React
Related Stack Overflow leveraging Axios

Issues with req.query, req.params, req.body using express+axios in MERN app

I am trying to make the following call in React using axios:
axios.get(`http://localhost:5000/daily_batches/num_tweets_by_tag_and_date/`, {
params: {
tag: "Green/Sustainable Energy",
date: "2021-05-07"
}
})
.then(res => {
console.log(res)
})
Should I use req.params, req.body or req.query in my express route for the axios call to be successful? If I use req.params, like so:
router.route('/num_tweets_by_tag_and_date/').get(async (req, res) => {
try {
const tag = req.params.tag;
const date = req.params.date;
const tag_num_tweets_field = "tags." + tag + ".num_tweets"
const num_tweets_data = await DailyBatch.findOne({"date": date}, {[tag_num_tweets_field]: 1, "_id": 0});
res.json(num_tweets_data)
} catch (err) {
console.log(err)
res.status(400).json({
errors: {
global: "An error occurred."
}
})
}
});
I get a "data:null" as shown in this screenshot. And indeed by doing console.log(req.params.tag) and console.log(req.params.date) in the route I get "undefined"
If I use req.body.tag and req.body.date instead of req.params.tag and req.params.date, I still get "undefined" when I console.log(date) and console.log(tag). And the api response is still "data:null". However, in Insomnia the api call works just fine and returns the expected data, as shown in the pic below:
If I use req.query.date and req.query.tag, I successfully get the data in the axios api call I make in React. However, the call does not work in Insomnia, where I get "null".
I cannot seem to find a way to make the get call in React using axios and passing a dictionary with the call parameters, while at the same time being able to make calls to the same endpoint in Insomnia. The only way I found is to use req.param("tag") and req.param("date") but apparently it's deprecated and so I would not want to use that.
I have read multiple posts about req.params, req.body and req.query but the more I read about this the more I am getting confused. Any help would be appreciated!
Probably req.body.
req.params is used if you set up your route using variables, such as
router.get('/users/:id', (req, res)=>{})
you can console.log(req.body) to check.
req.params is used when you have dynamic route .
Example= /:num_tweets_by_tag_and_delete
check here: Node.js: req.params vs req.body

React.js: Accessing the Retry-After header in an API response

I'm a newer programmer using React.js and the Spotify API for a music app. I’m trying to access the Retry-After header in a 429 rate-limiting error response (Spotify docs here). This is my code currently, which I loosely copied from this article.
async getArtistArt (artistID) {
let url = `https://api.spotify.com/v1/artists?ids=${artistID}`
let response = await fetch(url, {
headers: {
'Authorization': 'Bearer ' + localStorage.getItem('accessToken')
},
});
let data = await response.json();
if (Object.keys(data)[0] === "error" && data.error.status === 429) { // Handle rate limiting
console.log('Error!');
console.log(data);
for (var pair of data.headers.entries()) {
console.log(pair);
console.log(pair[0]);
}
}
return data;
}
This is what I see in the console:
screenshot here
console.log('Error!'); // Logs 'Error!'
console.log(data); // Logs error object, but not the header
console.log(pair) // Error that says 'Uncaught (in promise) TypeError: Cannot read property 'entries' of undefined'
I've tried not putting the response into json but that seemed to have no effect.
I've tried to avoid try/catch error statements as I’ve heard they’re somewhat outdated and not usually recommended, but would I need them to access the response header?
I would be grateful for any advice. A big thank you in advance!
I think I found where your error is. The code is almost correct except that you are checking for the headers in the json of the response.
Try to do for (var pair of response.headers.entries()) {...}, so that you are taking the response rather than its json content.

AXIOS request method changes to 'OPTIONS' instead of 'GET'

I am trying to call and API with react app(TSX) using Axios(this the first time I am using Axios) every time I run the app the method changes to 'OPTIONS' and the request becomes invalid. Help will be appreciated. Sharing my code sorry I am hiding the Auth Token for security reasons.
Code
import React, { useState, useEffect } from 'react';
import axios from 'axios';
interface Brands {
BrandId: number;
Name: string;
}
const AUTH_TOKEN = Something hiden for security;
var baseUrl = axios.defaults.baseURL = 'https://fppdirectapi-prod.fuelpricesqld.com.au/Subscriber/GetCountryBrands?countryId=21';
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.headers.get['Content-Type'] = 'application/json';
axios.defaults.method = 'get';
const FetchFuelType = () => {
const [brands, setPosts] = useState<Brands[]>([]);
useEffect(() => {
axios.get(baseUrl)
.then(res => {
console.log(res)
setPosts(res.data)
})
.catch(err => {
console.log(err)
})
}, [])
return (
<div>
<ul>
{brands.map(Brand => (<li key={Brand.BrandId}>{Brand.Name}</li>))}
</ul>
</div>
);
};
export default FetchFuelType;
Attached image of response
OPTIONS request is part of the so-called preflight request which is needed to figure out the CORS headers to know what needs/is allowed to be sent to the server with the actual GET request. Thats why you normally see two requests in your network tab (depending on your setting)
In your example it seems you have not configured anything CORS related on your server (thus the 405) or specifically have forbidden anything other than GET/POST requests. Or potentially the site has forbidden others to access its data
Usually, options request is sent before get automatically by axios, to get some preliminary data before firing get call. Check this https://github.com/axios/axios/issues/475.
The OPTIONS request is an inherent request generated by browser.
Browser uses the OPTIONS call to find out what methods are allowed by the server.
The API server meant for supporting requests from browser must allow OPTIONS in addition to the actual method (GET / POST / etc).
If the server does not support OPTIONS, then it may not support browser.
A server that does not support OPTIONS can only support non-browser clients
(Examples: REST client in Java / nodejs)
How to solve the problem?
The problem of '405 - OPTIONS method not allowed' can be solved in one of these 2 ways:
Update the server to support OPTIONS method (Recommended for server that is supposed to support browsers)
Develop an 'Intermediary REST client' which will request data from the server, on behalf of the browser
Browser <--> REST client (supports OPTIONS, POST) <--> Actual web service (does not support OPTIONS)

Resources