I have a frontend app with many API requests, but it's a pain to work with error responses.
Sometimes I need to go through many nested objects like: error.response.data.email and sometimes it is error.response.data.address.province[0].
I can't predict all of the errors, and writing manual "parsers" looks like a dirty extra solution to me:
const errorsObject = err.response.data
let errors = ''
Object.keys(errorsObject).map(key => {
if (key === 'address') {
Object.keys(errorsObject.address).map(
key => (errors += `${key} : ${errorsObject.address[key]}\n`)
)
} else {
errors += `${key} : ${errorsObject[key]}\n`
}
return undefined
})
yield toast.error(errors)
Also it still doesn't cover everything.
Is there any frontend parsers for that? If not, our backend is Python(Django), maybe there is a package for backend side? Ideally I'd want to see a flat array of objects {title, message}.
Your error objects are wellformed and the base idea of parsing the errors is right from a frontend perspective.
The only issues I see in your errors response is nesting and hydration.
If you want hydrated responses you should provide to your parser the feature of retrieving correctly data and eventually map it to frontend UI.
Usually I feed my forms with an object, usually called errors where I can safely retrieve the errors related to a field by its name.
IMHO you are doing it "right", try to work on object type instead of object specific key (like "address).
This is an example of a error parser I use very often. When a very deep nesting occurs or array parsing is needed, I usually update the parser to reach the error and gain the ability to retrieve it from my UI, for example to show the error under the field, border it in red and so on:
import _ from 'lodash';
export const getErrors = (errors) => {
const validationErrors = _.get(errors, 'data.validation_messages') ? errors.data.validation_messages : {};
// Iterate over object keys
_.forOwn(validationErrors, (value, key) => {
// Simple error: { key: { errorKey: value }} - Nested Error {key: { nestedErrorKey: { errorKey: value }}
const data = validationErrors[key][Object.keys(validationErrors[key])[0]];
// Check that content of key is neither null or object (nested validation)
if (validationErrors[key] !== null && typeof data !== 'object') {
validationErrors[key] = _.values(value)[0];
} else if (validationErrors[key] !== null && typeof data === 'object') { // TODO: Recursive approach ?
// Trasform nested values so obj: { nestedKey: nestedvalue } becomes obj_nestedKey: nestedValue
_.forOwn(validationErrors[key], (nestedValue, nestedKey) => {
validationErrors[`${key}_${nestedKey}`] = _.values(nestedValue)[0];
});
}
});
return validationErrors;
};
export default getErrors;
In my previous project, every error has an error code coming in the response and on the frontend side, every error code gets mapped with error messages, which was very easy to provide multi-locale error messages. You can try that if you have control on APIs and add one more key as error_code.
Related
I'm struggling to figure out how to prevent my app from crashing after fetching data from an API. Here's what I have done so far:
User searches for a word and based on that word, the program goes to a link with the searched phrase, then fetches and stores data in a const
var baseUrl = `https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=${search}&apikey=****`;
${search} = whatever the user enters in the searchbar
then baseUrl is the webite used to get all the data from API
useEffect(() => {
axios.get(baseUrl)
.then(res => {
setChart(res.data);
// console.log(res.data)
})
.catch(error => {
console.log("there was an error: " + error);
})
}, [baseUrl]);
useEffect(()=>{}, [chart]);
then the program loops thru the API and stores DATE and PRICE for each entry in const stockPrices and stockDates.
const stockPrices = useMemo(() => chart && Object.values(chart['Time Series (Daily)']).map(i => i['1. open']).reverse(), [chart]);
const stockDates = useMemo(() => chart && Object.keys(chart['Time Series (Daily)']).map(x => x.replace(/\d{4}-/, "")).reverse(), [chart]);
However, sometimes if user enter a search for which there's no existing link.. the app crashes, as it's trying to loop and display data that doesn't exist.
I'm not really sure how to handle this.
In the search component, I added this little "if" statement to stop it doing anything if the search is empty ( because no such link exists ):
const handleSearch = (e) => {
e.preventDefault();
if (e.target.value !== ``) {
setSearch(e.target.value.toUpperCase())
}};
However, this only solves a small part of the problem. If the app tries to fetch data from an invalid link it simply crashes.
when the app crashes - in the console it says
"Cannot convert undefined or null to object" which is reported from the line where const stockPrices and const stockDates are sitting on.
How can I stop the app from fetching data if a link doesn't exist - how to handle this bug ?
just for context the data stored in those variables is then passed to render a chart with prices (Y-axis) and dates(X-axis) so it needs to at least have some sort of replacement..
if(typeof stockDates === 'undefined') {
return ('undefined');
} else if(stockDates=== null){
return ('null');
}
I tried doing this to replace bad fetch with 'null' || 'undefined' but it's still crashing.
Please help.
IN SHORT: App crashes if it's trying to fetch data from a link that doesn't exist ( based on input from searchbar )
I'm not sure what error you're facing with the search problem.
The other one's the error you get when you pass undefined to Object.keys or Object.values function.
I'm gonna guess the API returns some data for invalid links so chart is not undefined. In the code, you're checking to make sure chart is not undefined. But most likely, chart['Time Series (Daily)'] is undefined.
I don't know enough about your requirements to suggest a fix. But you could add an additional check and make it so...
const stockPrices = useMemo(() => chart && chart['Time Series (Daily)'] && Object.values(chart['Time Series (Daily)']).map(i => i['1. open']).reverse(), [chart]);
const stockDates = useMemo(() => chart && chart['Time Series (Daily)'] && Object.keys(chart['Time Series (Daily)']).map(x => x.replace(/\d{4}-/, "")).reverse(), [chart]);
But I think it'd be better to fix the fetch code.
axios.get(baseUrl)
.then(res => {
if (res.data?.['Time Series (Daily)']) {
setChart(res.data);
}
else {
setChart(undefined);
//maybe set some error states so you can display the appropriate message?
}
})
I am trying to get the fully-qualified path of my Protobuf message type in JavaScript. For example, given the following file: status.proto
package my.messages.proto;
message Status {
string code = 1;
}
Once compiled with protoc, I can then do something like:
import { Status } from 'gen/my/messages/proto/status_pb.js';
const status = new Status();
But then how can I get the fully-qualified path of this message? I want a string of my.messages.proto.Status, but I can't seem to find any API where this is possible. I basically want the equivalent of the C++ function message.GetDescriptor()->full_name().
If I do console.log(status);, I see something printed out like:
my.messages.proto.Status {wrappers_: null, messageId_: undefined, arrayIndexOffset_: -1, array: Array(1), pivot_: 1.7976931348623157e+308, …}
So, the information is there, but just not sure how I can access it. Is this possible in JavaScript with Protobuf?
All the proto message types generated using protoc_node are available on the global proto namespace in node as a key:class object. So you can check whether your message is an instance of one of the key's values and return the key:
import { MessageType } from './MessageType_pb'
const message = new MessageType()
export function getSubjectFromMessage(message: Message){
for (const key in proto){
if(message instanceof proto[key]){
return key
}
}
}
getSubjectFromMessage(message) === "MessageType" // true
Nasty that they don't provide a class.name or something.
im trying to get data from this page
https://ahrefs.com/backlink-checker
its basically a website to check a domain rank and other status , when u enter a domain and click the check Check backlinks button it shows a google recaptcha
im using a captcha service to bypass this , problem is this site uses a callback on the captcha completion , when i recive the token from my api and put it in the #g-recaptcha-response i have to call the callback to move on there is no submit button
i used to find the callback in this object
___grecaptcha_cfg.clients[0].L.L.callback
and just call it like
page.evaluate(`___grecaptcha_cfg.clients[0].L.L.callback("${cap}")`)
but recently this obeject is nowhere to be found
and i get
Evaluation failed: TypeError: Cannot read property 'L' of undefined
any idea?
When I checked that url and when the captcha was there on the screen, then the object inside ___grecaptcha_cfg.clients[0] where callback was available was different i.e., L was not there on ___grecaptcha_cfg.clients[0], that's why you might have got the error. So thought of navigating to the callback object based on the type rather than directly accessing.
const client = ___grecaptcha_cfg.clients[0]
const keys = Object.keys(client)
const requiredKey = keys.find(key => client[key].constructor.name === "VK");
const requiredObj = client[requiredKey];
const callbackObjKey = Object.keys(requiredObj).find(key => requiredObj[key].callback);
requiredObj[callbackObjKey].callback("${cap}")
Hope this helps.
I have modified the code and used below approach to find the callback object, though this method is not so optimised but this is the way I could think to find out the callback method
const reduceObjectToArray = (obj) => Object.keys(obj).reduce(function (r, k) {
return r.concat(k, obj[k]);
}, []);
const client = ___grecaptcha_cfg.clients[0]
let result = [];
result = reduceObjectToArray(client).filter(c => Object.prototype.toString.call(c) === "[object Object]")
result = result.flatMap(r => {
return reduceObjectToArray(r)
})
result = result.filter(c => Object.prototype.toString.call(c) === "[object Object]")
const reqObj = result.find( r => r.callback)
reqObj.callback("${cap}")
I have a vue object with all these getters and setters, here is a screenshot from the console.log:
The structure of the actual DATA (the not-vue stuff) looks like this:
{
Internal_key: "TESTKEY_1",
extensiontable_itc: {
description_itc: "EXTENSION_ITC_1_1",
description_itc2: "EXTENSION_ITC_1_2",
},
extensiontable_sysops: {
description_sysops: "EXTENSION_SYSOPS_1"
}
}
The data might look different in other usecases.
There might be more or less key-value pairs on the outer object, and the keys might be named differently as well. Same goes for the nested objects and their contents.
Is there some convenient way to extract this data into a plain JS Object?
If not, how can I best loop the vue object to extract the data "manually"?
The AJAX request shall be performed by an axios request, if this is important as well.
EDIT:
Here is the relevant data in vue:
data() {
return {
editingRecord: {
original: null,
copy: null
}
}
}
During my programflow, both editingRecord.orginal and editingRecord.copy receive data from an inputform. copy sets its data to original if the user clicks the save/send button. Then, I want to take the data from editingRecord.original with both its keys and values and send them to the server via AJAX request.
Its best not to mix jQuery with Vue so here's how you would do it using Axios https://github.com/axios/axios
methods: {
submit() {
const body = this.editingRecord.original
// make changes to body
axios.post( "backend_url", body )
.then(function(resp){
console.log(resp)
});
}
}
Okay, I found the solution.
let vueObject = Object.entries(this.editingRecord.original)
for(const[key, value] of vueObject){
if(typeof value == 'object' && value !== null){
for(const [nestedKey, nestedValue] of Object.entries(value)){
result[nestedKey] = nestedValue
}
}else{
result[key] = value
}
}
console.log("resultObject is", result)
This way you can iterate over all properties including the properties of nested objects and reassign both key and value to a fresh, one-dimensional array.
I have multiple conditions in my firebase rules and want to access the data base on the current user by using if statement in my return statement. When I access it individually, it works but when I use if statements, I don't get the data but permission error issue.
I've tried accessing it but keeps failing.
My rule:
"query.orderByChild == 'userId' && query.equalTo == auth.uid ||
root.child('admins').child(auth.uid).child('role').val() === 'admin'"
Accessing the data:
let db = firebase.database().ref(`/items`).orderByChild('userId').equalTo(currentUser.uid)
if(firebase.database().ref('/admins').child(currentUser.uid).child('role').equalTo('admin')) {
db = firebase.database().ref(`/items`)
}
db
.once('value')
.then(snapshot => {
let doc = []
const data = snapshot.val();
for (let key in data) {
doc.push({
...data[key],
key: key
});
}
Ideally, I would like to have a rule with OR and have an if/else statement to return data depending on the user.
This statement does not do what you think it does:
if(firebase.database().ref('/admins').child(currentUser.uid).child('role').equalTo('admin'))
Nothing in this line reads from the database, and the equalTo merely builds a query, it does not actually perform any check.
If you want to to check whether the user is an admin:
let userRoleRef = firebase.database().ref('/admins').child(currentUser.uid).child('role');
userRoleRef.once('value').then((snapshot) => {
if ('admin' == snapshot.val()) {
db = firebase.database().ref(`/items`)
... continue using db here
}
}
Now the client-side query and the server-side security rules work in tandem to ensure the user only requests and only can get the data that they're authorized for.