How to get request headers for the Apollo GraphQL error? - reactjs

I need to get the request header on the client when an error occurs
const { data, error } = useQuery(gql`Query ...`);
// ..
if (error) {
// get the request headers here
// e.g. error.graphQLErrors[0].headers.requestId
}
I was trying to modify the error using onError link but this object is Read Only
import { onError } from '#apollo/client/link/error';
const errorLink = onError((errorHandler) => {
errorHandler.graphQLErrors[0].extensions = { requestId: ... }
})
I know I can extend extensions on the backend side but I need to generate the request ID on the client side.

There's an example similar to what you're describing on the Apollo docs. It doesn't use extensions specifically, but would enable you to generate the request ID mentioned.

Related

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

Communicating a successful workbox-background-sync replay to open clients

I'm using React 17, Workbox 5, and react-scripts 4.
I created a react app with PWA template using:
npx create-react-app my-app --template cra-template-pwa
I use BackgroundSyncPlugin from workbox-background-sync for my offline requests, so when the app is online again, request will be sent automatically.
The problem is I don't know when the request is sent in my React code, so I can update some states, and display a message to the user.
How can I communicate from the service worker to my React code that the request is sent and React should update the state?
Thanks in advance.
You can accomplish this by using a custom onSync callback when you configure BackgroundSyncPlugin. This code is then executed instead of Workbox's built-in replayRequests() logic whenever the criteria to retry the requests are met.
You can include whatever logic you'd like in this callback; this.shiftRequest() and this.unshiftRequest(entry) can be used to remove queued requests in order to retry them, and then re-add them if the retry fails. Here's an adaption of the default replayRequests() that will use postMessage() to communicate to all controlled window clients when a retry succeeds.
async function postSuccessMessage(response) {
const clients = await self.clients.matchAll();
for (const client of clients) {
// Customize this message format as you see fit.
client.postMessage({
type: 'REPLAY_SUCCESS',
url: response.url,
});
}
}
async function customReplay() {
let entry;
while ((entry = await this.shiftRequest())) {
try {
const response = await fetch(entry.request.clone());
// Optional: check response.ok and throw if it's false if you
// want to treat HTTP 4xx and 5xx responses as retriable errors.
postSuccessMessage(response);
} catch (error) {
await this.unshiftRequest(entry);
// Throwing an error tells the Background Sync API
// that a retry is needed.
throw new Error('Replaying failed.');
}
}
}
const bgSync = new BackgroundSyncPlugin('api-queue', {
onSync: customReplay,
});
// Now add bgSync to a Strategy that's associated with
// a route you want to retry:
registerRoute(
({url}) => url.pathname === '/api_endpoint',
new NetworkOnly({plugins: [bgSync]}),
'POST'
);
Within your client page, you can use navigator.seviceWorker.addEventListener('message', ...) to listen for incoming messages from the service worker and take appropriate action.

How to get error body using React Apollo Link Rest

I am creating an React app using Apollo Graphql and I am having some trouble with the Apollo Link Rest. The problem is that in the middle of my app I have to make a REST request, however this request can be sucessful and return a status code = 200 or it can fail, returning a status code = 400.
It is important for my app to show the end user an error message that is returned in this request's body in case of a 400 error. However when an error happens the Apollo Link Rest just throws an exception, but it doesn't return response body back to me.
Is there a way for me to get the response body, when there is an error with the Apollo Link Rest? I thought that I could get it from the result variable, but since the Apollo throws an exception, this variable never changes.
Here is my code:
const result = await context.client.query<MyQuery>({
query: MyQuery,
variables: {
input: {
companyId: variables.companyId,
},
},
});
query MyQuery($input: MyQueryInput!) {
myQuery(input: $input) #rest(
type: "QueryResponse",
method: "POST"
path: "v1/my-query"
) {
id
minimumPreparationTime
maximumPreparationTime
extras {
id
optional
key
}
warning
error {
id
type
message
}
}
}
Extending Greg comment and apollo-link-error, you can retrieve the error body from networkError.result.message.
You can use apollo-link-error to catch and handle server errors, network errors, and GraphQL errors. You should be able to conditionally set response.errors if needed.
import { onError } from "apollo-link-error";
const link = onError(({ graphQLErrors, networkError, response, operation }) =>
{
if (graphQLErrors)
graphQLErrors.map(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path:
${path}`,
),
);
if (networkError) console.log(`[Network error]: ${networkError}`);
response.error = // set error property here
});
It may also be worth noting Apollo exposes an errorPolicy option, for which the default is none. The policy can be modified to all which, according to the docs,
is the best way to notify your users of potential issues while still
showing as much data as possible from your server. It saves both data
and errors into the Apollo Cache so your UI can use them.

Book info Goodreads API

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

Resources