Handle error in axios.get and data received in react/js - javascript

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?
}
})

Related

React DOM not re-rendering after state change of array property

I do understand that this problem is very common and most people might find this as a duplicate but I am at my wits end and that is why I am here.
I have a React component called App is a functional component.
Start of App component
function App() {
const [results, setResults] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [name, setName] = useState('');
const [isNameSelected, setIsNameSelected] = useState(false);
...
There is a child component which is acting erratically at the moment and it is part of the return components of the App function.
Section of Code in return statement of the App component:
<ListGroup className="typeahead-list-group">
{!isNameSelected &&
results.length > 0 &&
results.map((result: Result) => (
<ListGroupItem
key={result.cik}
className="typeahead-list-group-item"
onClick={() => onNameSelected(result)}
>
{result.cik + ' | ' + result.name}
</ListGroupItem>
))}
</ListGroup>
A change to to results is handled by the component FormControl's onChange function here also part of the return statement of App:
<FormControl
placeholder="Entity/CIK"
id="searchInput"
type="text"
autoComplete="off"
onChange={handleInputChange}
value={name}
/>
handleInputChange is defined in App function as:
const handleInputChange = (e: any) => { // Triggers when search bar is changed
// remove error bubble
setAlertMessage(new AlertData('', false));
const nameValue = e.target.value; // get the new input
setName(nameValue); // set the new input
// even if we've selected already an item from the list, we should reset it since it's been changed
setIsNameSelected(false);
setResults([]); // clean previous results
if (nameValue.length > 1) { // if the input is more than 1 character
setIsLoading(true); // set loading to true
updateSearchInput(nameValue) // get the results
.then((res) => {
setResults(res as React.SetStateAction<never[]>); // set the results
setIsLoading(false); // set loading to false
})
.catch((error) => {
// error bubble
let strError = error.message;
strError = strError.split(':').pop();
let errorMessage: AlertData = new AlertData(strError, true); // create error message for empty search
setAlertMessage(errorMessage); // set alert message
// loading spinner
setIsLoading(false);
});
}
}
However when there is an input change in form control, like typing in an entire word, the search functionality works, populating the DOM with suggested words. However when I clear the value in FormControl really fast (by pressing Backspace/Delete several times in quick succession), then the search results stay. Doing it slow or selecting and clearing it all at once however does not show this erratic behavior.
I have used console.log to print out the value of results in the an empty component like this:
{console.log(results) && (<div><div/>)}
in return statement of App to see what the contents of results are. However it does show that results value were not updated by setResults().
This problem however does not exist for the other states utilized here. Why?
EDIT
From the answer accepted below from #ghybs. This is a timeline of what might be happening with the call:
Enter search
await call runs but request response is slow so takes a while.
Delete all the keyword in search
results is made empty with setResults([]) in handleInputChange call.
await call finishes. setResults(res as React.SetStateAction<never[]>) runs making results non-empty.
You very probably just have plenty concurrent requests (1 per key stroke, including back space?), and unordered results from your updateSearchInput async function: the last received one overwrites your results, but that one may not originate from your last key stroke (the one that removed the last character from your textarea).
Typically if an empty search is faster than a search with plenty words, the results from empty input do clear your results, but then these are filled again by the results of a previous search.

React hook form - watch method rerender if there is less than 2 element in combobox

Today I am faced with a really strange issue and I can not solve it. I am using react query to fetch data for my Combobox.
const { isLoading, data } = useQuery('loadBranchBICRMA', loadBranchBIC)
and I am using form.watch for catching if there are any changes occurring in my Combobox because if there are some changes occurring I will check my DB and fill the rest of the form if there is a match.
const receiverBIC = form.watch('ReceiverBIC');
const senderBIC = form.watch("SenderBIC") ;
const [branchColumns] = React.useState(['none']);
const [messageTypeColumns] = React.useState(['accptdMsg','declndMsg']);
const validateRMA = () => {
const URL = VALIDATE_RMA + `/${senderBIC}/${receiverBIC?.length === 8 ? receiverBIC+"XXX": receiverBIC}`
axios.get(URL, {httpsAgent})
.then((response) => {
if (response.data !== ''){
setSelectedAcceptedMessages(response.data.accptdMsg);
setSelectedDeclinedMessages(response.data.declndMsg);
setIsSave(true)
}else setIsSave(false);
}).catch();
}
useEffect(() => {if ( receiverBIC?.length !== 0 ) {validateRMA();}}, [receiverBIC,senderBIC]);
The problem is if there are less than 2 elements in my Combobox, form.watch rerender and crash my app. The strange part is why 2 elements :D any help will save me thx.
I still didnt understand why this problem occurring while less than 2 item in combobox but the solution is using useWatch instead of watch. It solved all my problems.

Implementing live search using Next js

I'm new to Next js and im trying to implement a live search input field, I'm not sure if I have done things right if im violating some principles of next by not using getServerProps for this matter, what I have currently done is simply call an API and save the result in a simple state
const handleInputChange = (event) => {
const query = event.target.value;
setQuery(query);
if (event.target.value.length < 3) {
setResults([]);
return;
}
setLoading(true);
fetchSearchResults(query);
};
const fetchSearchResults = async (paginate = "", query) => {
if (query === "") {
setResults([]);
}
const searchUrl = [uri];
await axios
.get(searchUrl)
.then((res) => setResults(res.data.results))
.catch((error) => setMessage("No data found"));
};
Is there a better way to do such thing in next js(production ready)? Thanks
edit:
I found a way that could probably solve this by using getStaticProps with revalidate, however this is a component in the header that's on all pages and not a page, getStaticProps won't work here, is there a way around this?
To be clear I have a list of products, and the header contains a search bar that would function as an input for live search of these products

finding recaptcha callback

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}")

How to deal with standard Error object on frontend?

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.

Categories

Resources