Error handling API fetch request with React - javascript

I'm using React, and just wanted some advice on error handling.
I have my fetch request in an async function, this function is in another folder and is being imported in my App.js file. Im doing this because I want to try out testing with mock service worker, and have read its easier with the requests in a separate file.
From looking at my code below, is this best practice for error handling? Is there a better way thats more concise?
Here is my fetch async function, at the moment i've purposely gave the wrong env variable name so it will give me a 401 unauthorised error.
require('dotenv').config()
export const collect = async () => {
const key = process.env.REACT_APP_API_KE
try{
const res = await fetch(`http://api.openweathermap.org/data/2.5/weather?q=london&appid=${key}`)
if(res.status !== 200){
throw new Error(res.status)
}
const data = await res.json()
return data
} catch (error){
let err = {
error: true,
status: error.message,
}
return err
}
}
This is being called in my App.js file (not rendering much at the moment)
import { useState } from 'react'
import { collect } from './utilities/collect'
require('dotenv').config()
function App() {
const [data, setData] = useState("")
const [error, setError] = useState({ error: false, status: "" })
const handleFetch = async () => {
let newData = await collect()
if(newData.error){
setError({ error: newData.error, status: newData.status })
}else {
setData(newData)
}
}
return (
<div className="App">
<h1>weather</h1>
<button onClick={handleFetch}>fetch</button>
</div>
);
}
export default App;
Any help or advise would be great.

When writing an abstraction around Promises or async and await one should make sure it is used appropriately, that is a good Promse must allow it consumer to use it then and catch method or should allow it consumer use try and catch to consume it and provide appropriate information on Errors
From Your code, The abstraction doesnt gives back an appropriate response and doesnt follow the standard behavior of a promise it always resolve and never reject and though the code works its implementation of the collect is different from a standard Promise and wont be nice for a standard code base, for example a good abstraction will provide error information returned from the third party api
Appropriate way to amend code
The third party api returns this response
{
"cod":401,
"message": "Invalid API key. Please see http://openweathermap.org/faq#error401 for more info."}
This should be your implementation
// Your fetch functon abstraction modified
require('dotenv').config()
const collect = async () => {
const key = process.env.REACT_APP_API_KE;
const res = await fetch(
`http://api.openweathermap.org/data/2.5/weather?q=london&appid=${key}`,
);
if (res.status !== 200) {
const error = await res.json();
throw {message: error.message,status:error.cod};
}
const data = await res.json();
return data;
};
Your app component should now be like this
import { useState } from 'react'
import { collect } from './utilities/collect'
require('dotenv').config()
function App() {
const [data, setData] = useState("")
const [error, setError] = useState({ error: false, status: "" })
const handleFetch = async () => {
try {
let newData = await collect()
setData(newData)
} catch(e){
setError({ error: e.message, status: e.status })
}
}
return (
<div className="App">
<h1>weather</h1>
<button onClick={handleFetch}>fetch</button>
</div>
);
}
export default App;

Related

React Prop returning Null as it relies on state

Hopefully a simply one.
I make an API call in my component which brings down some account information such as AccountUid, Category etc, i use state to set these.
useEffect(() => {
fetch(feed_url, {
headers: {
//Headers for avoiding CORS Error and Auth Token in a secure payload
"Access-Control-Allow-Origin": "*",
Authorization: process.env.REACT_APP_AUTH_TOKEN,
},
})
//Return JSON if the Response is recieved
.then((response) => {
if (response.ok) {
return response.json();
}
throw response;
})
//Set the Account Name state to the JSON data recieved
.then((accountDetails) => {
setAccountDetails(accountDetails);
console.log(accountDetails.accounts[0].accountUid);
console.log(accountDetails.accounts[0].defaultCategory);
})
//Log and Error Message if there is an issue in the Request
.catch((error) => {
console.error("Error fetching Transaction data: ", error);
});
}, [feed_url]);
This Works perfectly well and it Logs the correct values in my .then when testing it.
The issue however is that i want to pass these down as props. But i get an error that they are being returned as null (My default state).. i presume as they're jumping ahead.
<div className="App">
<GetAccountName
accountUID={accountDetails.accounts[0].accountUID}
defCategory={accountDetails.accounts[0].defaultCategory}
/>
</div>
How do i pass the the 2 details im logging as props?? I've tried setting default state to "" instead of null and just get that it is undefined.
If you dont want to use conditional render in your child component, so you should try optional chaining
<GetAccountName
accountUID={accountDetails?.accounts?.[0]?.accountUID}
defCategory={accountDetails?.accounts?.[0]?.defaultCategory}
/>
Since fetching is asyncronous, the most common way is to show some loading indicator (like a spinner) & once the data come in, show the component instead.
If you don't need an indicator, you might just return null.
The general idea is to manipulate some intermediary states (e.g. data, isError) based on the promise state.
Check out react-query library example or a lighter abstraction like useFetch hook to see how they manage it.
Here's a sample implementation of useFetch taken from this article:
const useFetch = (url, options) => {
const [response, setResponse] = React.useState(null);
const [error, setError] = React.useState(null);
const [abort, setAbort] = React.useState(() => {});
React.useEffect(() => {
const fetchData = async () => {
try {
const abortController = new AbortController();
const signal = abortController.signal;
setAbort(abortController.abort);
const res = await fetch(url, {...options, signal});
const json = await res.json();
setResponse(json);
} catch (error) {
setError(error);
}
};
fetchData();
return () => {
abort();
}
}, []);
return { response, error, abort };
};

Uncaught (in promise) ReferenceError: Buffer is not defined. Using Phantom Wallet, Solana and React to make a transaction

I created a simple CRA app to figure out how to use the Phantom wallet to make Solana transactions(I still don't know If that is even possible). I referenced two Stackoverflow questions source and source and came up with a somewhat working code as in the airdrop is working perfectly fine but the main transaction is not.
This is my code:
import "./App.css";
import { useEffect, useState } from "react";
const web3 = require("#solana/web3.js");
function App() {
const [provider, setProvider] = useState(null);
const [walletKey, setWalletKey] = useState(null);
const getProvider = () => {
if ("solana" in window) {
const provider = window.solana;
if (provider.isPhantom) {
return provider;
}
}
};
const connectWallet = async () => {
const provider = getProvider();
if (provider) {
try {
const response = await provider.connect();
const pubKey = await provider.publicKey;
console.log(pubKey);
setProvider(provider);
setWalletKey(response.publicKey.toString());
} catch (err) {
// { code: 4001, message: 'User rejected the request.' }
}
}
};
useEffect(() => connectWallet, []);
const airDropSol = async (connection, publicKey) => {
try {
const airdropSignature = await connection.requestAirdrop(
publicKey,
web3.LAMPORTS_PER_SOL
);
const latestBlockHash = await connection.getLatestBlockhash();
// Confirming that the airdrop went through
await connection.confirmTransaction({
blockhash: latestBlockHash.blockhash,
lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
signature: airdropSignature,
});
console.log("Airdropped");
} catch (error) {
console.error(error);
}
};
async function transferSOL() {
//Changes are only here, in the beginning
const phantomProvider = provider;
if (!phantomProvider) {
console.log("No provider found", phantomProvider);
}
const pubKey = await phantomProvider.publicKey;
console.log("Public Key: ", pubKey);
// Establishing connection
var connection = new web3.Connection(web3.clusterApiUrl("devnet"));
// I have hardcoded my secondary wallet address here. You can take this address either from user input or your DB or wherever
var recieverWallet = new web3.PublicKey(
"Wallet Key I want to send SOL to here"
);
// Airdrop some SOL to the sender's wallet, so that it can handle the txn fee
airDropSol(connection, pubKey);
console.log("WORKED 1");
const transaction = new web3.Transaction();
const instructions = web3.SystemProgram.transfer({
fromPubkey: pubKey,
toPubkey: recieverWallet,
lamports: web3.LAMPORTS_PER_SOL, //Investing 1 SOL. Remember 1 Lamport = 10^-9 SOL.
});
transaction.add(instructions);
console.log("WORKED 2");
// Setting the variables for the transaction
transaction.feePayer = pubKey;
let blockhashObj = await connection.getLatestBlockhash();
transaction.recentBlockhash = blockhashObj.blockhash;
const signature = await phantomProvider.signAndSendTransaction(
connection,
transaction,
[pubKey]
);
await connection.confirmTransaction(signature);
console.log(signature);
}
return (
<div className="App">
<header className="App-header">
<h2>Tutorial: Connect to Phantom Wallet</h2>
{provider && walletKey && <p>Connected account {walletKey}</p>}
{provider && walletKey && <button onClick={transferSOL}>TEST</button>}
{!provider && (
<p>
No provider found. Install{" "}
Phantom Browser extension
</p>
)}
</header>
</div>
);
}
export default App;
This is the error generated in and I don't know how to fix it. Can anyone help? Error in browser console
Also when I run npm start this error in vcode console also gets shown.
All I want to do is simply send SOL from one phantom wallet to another like sending money in real life, I have spent alot of time trying to figure this out someone please point out the problem and help
Use #solana/web3.js version 1.30.2
New versions form web3 libraries need some dependencies and webpack configurations.
You can use a code I'm sharing:-
Refer my code on this link
With Webpack 5 you need to polyfill Buffer.
Here is a tutorial on how to do so https://viglucci.io/how-to-polyfill-buffer-with-webpack-5
And here is an example of a webpack config of a Solana dApp UI made with CRA https://github.com/Bonfida/serverless-merch/blob/master/ui/config-overrides.js

How to display Metaplex NFT in React using #metaplex/js and programs.metadata.Metadata

I am trying to run this 'getting started' example from the docs. However I think there's been a change and programs.metadata.Metadata (shown there) no longer works.
https://docs.metaplex.com/sdk/js/getting-started
They suggest this:
import { Connection, programs } from '#metaplex/js';
const connection = new Connection('devnet');
const tokenPublicKey = 'Gz3vYbpsB2agTsAwedtvtTkQ1CG9vsioqLW3r9ecNpvZ';
const run = async () => {
try {
const ownedMetadata = await programs.metadata.Metadata.load(connection, tokenPublicKey);
console.log(ownedMetadata);
} catch {
console.log('Failed to fetch metadata');
}
};
run();
I have this in my React app:
import { Connection, programs } from '#metaplex/js';
const connection = new Connection('devnet');
const tokenPublicKey = 'Gz3vYbpsB2agTsAwedtvtTkQ1CG9vsioqLW3r9ecNpvZ';
const run = async () => {
try {
const ownedMetadata = await programs.metadata.Metadata.load(connection, tokenPublicKey);
console.log(ownedMetadata);
} catch(error) {
console.log('Failed to fetch metadata');
console.log(error);
}
};
function App() {
return (
<div className="App">
<p onClick={run}>would be cool if it worked</p>
</div>
);
}
export default App;
I get an error as though programs.metadata.Metadata doesn't exist - "Cannot read properties of undefined (reading 'Metadata')". I even took this out of React and did a plain node script to just run the example code, which fails in the same way.
Any idea on how I could fix this?
For users that find this question via search:
There is now a better solution than using #metaplex-foundation/mpl-token-metadata directly. Have a look at #metaplex/js-next findByMint
Those lines should be all you need. imageUrl would be the image path.
import { Metaplex } from "#metaplex-foundation/js-next";
import { Connection, clusterApiUrl } from "#solana/web3.js";
const connection = new Connection(clusterApiUrl("mainnet-beta"));
const metaplex = new Metaplex(connection);
const mint = new PublicKey("ATe3DymKZadrUoqAMn7HSpraxE4gB88uo1L9zLGmzJeL");
const nft = await metaplex.nfts().findByMint(mint);
const imageUrl = nft.metadata.image;

How to convert .then .catch to async/await

Working on a login form / logout button with React/Redux front end and my own nodejs/express api. Having an issue with the login form. Most of the time it works just fine, but I'm getting erros on a regular basis. First error is forbidden, which tells me that the user is not quite authenticated before send the userDetails request.
Then there's another bug where Redux doesn't change the role of the user, which I need to dynamically render the nav. I'm thinking converting handleLogin to async/await will be the solution, but I believe I'm not doing it right.
import React from 'react';
import { login, userDetails } from '../axios/homeApi';
import { useForm } from 'react-hook-form';
import { useDispatch } from 'react-redux';
import { setLogin, setRole } from '../redux/actions';
const LoginForm = () => {
const { handleSubmit, register, errors } = useForm();
const dispatch = useDispatch();
const handleLogin = values => {
login(values.email, values.password)
.then(res => {
const token = res.data.token;
window.localStorage.setItem('auth', token);
dispatch(setLogin({ loggedIn: true }));
userDetails()
.then(res => {
const role = res.data.data.role;
dispatch (setRole({ role }));
})
})
}
return (
<div>
<form action="" onSubmit={handleSubmit(handleLogin)} className="footer-form">
<input
type="email"
placeholder="Enter Email Here"
name="email"
ref={register({ required: "Required Field" })}
/>
<input
type="password"
placeholder="Enter Password Here"
name="password"
ref={register({
required: "Required Field",
minLength: { value: 6, message: "Minimum Length: 6 Characters" }
})}
/>
{errors.password && errors.password.message}
{errors.email && errors.email.message}
<input type="submit" value="Login" />
</form>
</div>
)
}
export default LoginForm;
Here's my best attempt at converting handleLogin to async/await. I'm trying to understand how I'm supposed to pull data from these calls.
const handleLogin = async values => {
try {
const {data: {token}} = await login(values.email, values.password)
window.localStorage.setItem('auth', token);
console.log(token);
const user = await userDetails();
await dispatch(setLogin({ loggedIn: true}))
await dispatch(setRole(user.data.data.role))
} catch(err) {
console.log(err)
}
}
Any help/guidance on this would be greatly appreciated.
You have to think when you use await, the variable value is the same that returned into res without await.
So if you have:
login(values.email, values.password)
.then(res => {
})
This is like:
var login = await login(values.email, values.password);
So using this logic, this:
login(values.email, values.password)
.then(res => {
const token = res.data.token;
// whatever
userDetails()
.then(res => {
const role = res.data.data.role;
// whatever
})
})
Turn into:
var login = await login(values.email, values.password)
const token = login.data.token;
// do whatever
var userDetails = await userDetails()
const role = userDetails.data.data.role;
// whatever
Check how works this example. The code is "the same". One using .then and the other using await.
runThenFunction();
runAwaitFunction();
function runThenFunction(){
console.log("Enter then function")
this.returnPromise(2000).then(res => {
console.log(res);
this.returnPromise(1000).then(res => {
console.log(res);
});
});
//You can log here and it will be displayed before the promise has been resolved
console.log("Exit then function")
}
async function runAwaitFunction(){
console.log("Enter await function")
var firstStop = await returnPromise(1000);
console.log(firstStop)
var secondStop = await returnPromise(4000);
console.log(secondStop)
// Using await the code "stops" until promise is resolved
console.log("Exit await function")
}
function returnPromise(time){
return new Promise(resolve => setTimeout(() => resolve("hello: "+time+"ms later."), time));
}
Looks like you may have already have an answer, but it looks to me like it may be because you are not waiting on your dispatch of setLogin to complete. I don't know how your setLogin method is setup, but it would have to be a thunk. I found the following post that explains it well.
How to return a promise from an action using thunk and useDispatch (react-redux hooks)?
Consider any object with a then property, which is a function that accepts a callback as its 1st parameter, for example:
let obj = {
then: callback => callback('hello')
};
await converts any such object into the value then provides to the callback. Therefore:
(await obj) === 'hello'
In your example there are two instances where you require the value returned to a then callback:
login(...).then(res => { /* got res */ });
and
userDetails().then(res => { /* got res */ });
Think of await simply as a way of getting the value returned to an object's then callback! In this case the objects are the result of login(...) and userDetails(), and you can convert to:
let res = await login(...);
and
let res = await userDetails();
You can see this also saves a bunch of indentation, one of many reasons people enjoy using async / await!
These conversions from the then-callback-value to await, when inserted into your code, look like:
const handleLogin = async values => {
let loginRes = await login(values.email, values.password);
let token = loginRes.data.token;
window.localStorage.setItem('auth', token);
dispatch(setLogin({ loggedIn: true }));
let userDetailsRes = await userDetails();
let role = userDetailsRes.data.data.role;
dispatch(setRole({ role }));
};
Note that the function must be marked async, and that I've renamed res to a more specific name, since both responses now exist in the exact same scope, and need to be differentiated from each other.
Overall whenever you use then to get ahold of some value in a callback, you are able to convert to the more elegant await equivalent.

How to handle Promise in custom react useEffect

I have two components which i am working with. In the first component, i made a custom useEffect hook that retrieves data from my server. Please see the code below:
Code snippet One
import {useState, useCallback} from 'react';
import {stageQuizApi} from '../api/quiz';
import {QuestionService} from "../services/IdDbServices/question_service";
const usePostData = ({url, payload, config}) => {
const [res, setRes] = useState({data: null, error: null, isLoading: false});
const callAPI = useCallback(() => {
setRes(prevState => ({...prevState, isLoading: true}));
stageQuizApi.patch(url, payload, config).then( res => {
setRes({data: res.data, isLoading: false, error: null});
const questionInDb = {};
const {NoTimePerQuestion,anwser, question, playerDetails, option} = res.data.data;
const {playerid,anwserRatio, name} = playerDetails
questionInDb.timePerQuestion = NoTimePerQuestion;
questionInDb.anwserRatio = anwserRatio;
questionInDb.options = option;
questionInDb.answer = anwser;
questionInDb.playerId = playerid;
questionInDb.name = name;
questionInDb.question = question;
const Service = new QuestionService();
Service.addStudent(questionInDb).
then(response=>console.log(response))
.catch(err=>console.log(err));
}).catch((error) => {
console.log(error)
if (error.response) {
const errorJson = error.response.data
setRes({data: null, isLoading: false, error: errorJson.message});
} else if (error.request) {
setRes({data: null, isLoading: false, eror: error.request});
} else {
setRes({data: null, isLoading: false, error: error.message});
}
})
}, [url, config, payload])
return [res, callAPI];
}
export default usePostData;
The above module has two purpose. It first makes an axios request to my endpoint and secondly makes a database insertion to browser IndexDb (similar to localstorage but with sql approach) ( like inserting data into the database using the response that was gotten from the first request. so typically i have a promise in the outer .then block. This part:
Code snippet Two
const questionInDb = {};
const {NoTimePerQuestion,anwser, question, playerDetails, option} = res.data.data;
const {playerid,anwserRatio, name} = playerDetails
questionInDb.timePerQuestion = NoTimePerQuestion;
questionInDb.anwserRatio = anwserRatio;
questionInDb.options = option;
questionInDb.answer = anwser;
questionInDb.playerId = playerid;
questionInDb.name = name;
questionInDb.question = question;
const Service = new QuestionService();
Service.addStudent(questionInDb).
then(response=>console.log(response))
.catch(err=>console.log(err));
Here is the problem, I am trying to maintain state as i want the result of this module to be shared in another route and i don't want to hit the server again hence i inserted the result into indexDb browser storage. Here is the code that executes the above module:
Code snippet Three
const displaySingleQuestion = ()=>{
OnUserGetQuestion();
history.push('/player/question');
}
The above method is called from my first route /question and it is expected to redirect user to the /player/question when the displaySingleQuestion is called.
On the new route /player/question i then want to fetch the data from IndexDb and update the state of that component using the useEffect code below:
Code snippet Four
useEffect(()=>{
const getAllUserFromIndexDb = async()=>{
try{
const result = await new Promise((resolve, reject) => {
service.getStudents().then(res=>resolve(res)).catch(err=>reject(err))
});
console.log('it did not get to the point i was expecting',result)
if(result[0]){
console.log('it got to the point i was expecting')
const singleQuestion = result[0];
const questionPage = playerQuestionToDisplay;
questionPage.name = singleQuestion.name;
questionPage.anwserRatio = singleQuestion.anwserRatio;
questionPage.answer = singleQuestion.answer;
questionPage.options = singleQuestion.options;
questionPage.playerId = singleQuestion.playerId;
questionPage.question = singleQuestion.question;
questionPage.timePerQuestion = singleQuestion.timePerQuestion;
return setplayerQuestionToDisplay({playerQuestionToDisplay:questionPage})
}
}
catch(error){
console.log(error)
}
}
getAllUserFromIndexDb();
return function cleanup() {
setplayerQuestionToDisplay({playerQuestionToDisplay:{}})
}
},[history.location.pathname]);
The problem is that only one Button click (Code snippet three)(displaySingleQuestion()) triggers the whole functionality and redirect to the /player/question page but in this new route the state is not been set until a page reload as occurred, i tried debugging the problem and i found out that when the button is clicked i found out that Code snippet two is executed last hence when Code snippet Four ran it was in promise and until a page reloads occurs the state of the component is undefined
Thanks for reading, Please i would appreciate any help in resolving this issue.

Categories

Resources