Catch block executes if dispatch function is used in try block - javascript

I don't know why the below code works properly without any error and response is also recorded once the loginHandler() is fired but once I add the dispatch function inside the try block after the response has got, the catch block with the check whether a response has been got or not executes!
function Login() {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [msg, setMsg] = useState("");
const dispatch = useDispatch();
const LoginHandler = async (e) => {
e.preventDefault();
try {
const response = await axios.post(
LOGIN_URL,
JSON.stringify({ username, password }),
{
headers: { "Content-Type": "application/json" },
withCredentials: false,
}
);
console.log(JSON.stringify(response?.data?.access));
dispatch(
login({
username: username,
accessToken: response?.data?.access,
})
);
const userName = useSelector((state) => state.user.value.userName);
const accessToken = useSelector(
(state) => state.user.value.userAccessToken
);
console.log("USER NAME STORED IN STORE = " + userName);
console.log("USER ACESS TOKEN STORED IN STORE = " + accessToken);
setMsg("Login Successful!");
} catch (err) {
if (!err?.response) {
console.log("NO SERVER RESPONSE");
setMsg("NO SERVER RESPONSE!");
} else {
console.log("SOMETHING WRONG!");
setMsg("SOMETHING WENT WRONG!");
}
}
};
return (
<LoginForm />
);
}
export default Login;
The Error I get is,
react-dom.development.js:16227 Uncaught (in promise) Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
at Object.throwInvalidHookError (react-dom.development.js:16227:9)
at useContext (react.development.js:1618:21)
at useReduxContext (useReduxContext.js:21:24)
at useSelector2 (useSelector.js:40:9)
at LoginHandler (Login.jsx:49:22)
I have also tried to put the dispatch function inside another function and then call it, but the problem persists!

The issue it seems is that the code is attempting to use the useSelector hook in a nested function, which breaks the Rules of Hooks.
Only Call Hooks at the Top Level
Don’t call Hooks inside loops, conditions, or nested functions.
Instead, always use Hooks at the top level of your React function,
before any early returns. By following this rule, you ensure that
Hooks are called in the same order each time a component renders.
That’s what allows React to correctly preserve the state of Hooks
between multiple useState and useEffect calls.
Move the useSelector calls out of the callback, or just reference the response values directly.
Use the useEffect hook if you care to log the updated state values.
Example:
function Login() {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [msg, setMsg] = useState("");
const dispatch = useDispatch();
const userName = useSelector((state) => state.user.value.userName);
const accessToken = useSelector(
(state) => state.user.value.userAccessToken
);
useEffect(() => {
console.log("USER NAME STORED IN STORE = " + userName);
console.log("USER ACCESS TOKEN STORED IN STORE = " + accessToken);
}, [userName, accessToken]);
const LoginHandler = async (e) => {
e.preventDefault();
try {
const response = await axios.post(
LOGIN_URL,
JSON.stringify({ username, password }),
{
headers: { "Content-Type": "application/json" },
withCredentials: false,
}
);
console.log(JSON.stringify(response?.data?.access));
dispatch(
login({
username: username,
accessToken: response?.data?.access,
})
);
console.log("USER NAME USED = " + username);
console.log("USER ACCESS TOKEN RETURNED = " + response?.data?.access);
setMsg("Login Successful!");
} catch (err) {
if (!err?.response) {
console.log("NO SERVER RESPONSE");
setMsg("NO SERVER RESPONSE!");
} else {
console.log("SOMETHING WRONG!");
setMsg("SOMETHING WENT WRONG!");
}
}
};
return (
<LoginForm />
);
}
export default Login;

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 };
};

Why is the account address from metamask coming back as undefined after storing it in useState()

I have this piece of code that is connecting to metamask wallet and sets the account address using useState().
const [currentAccount, setCurrentAccount] = useState("")
const connectWallet = async () => {
try {
if (!ethereum) return alert("Please install MetaMask.")
const accounts = await ethereum.request({ method: "eth_requestAccounts" })
setCurrentAccount(accounts[0])
console.log(accounts)
// TODO:Add conditional statement to check if user has token
navigate("/portfolio")
} catch (error) {
console.log(error)
throw new Error("No ethereum object")
}
}
console.log("current account", currentAccount)
const returnCollection = async (currentAccount) => {
const options = { method: 'GET', headers: { Accept: 'application/json' } };
fetch(`https://api.opensea.io/api/v1/collections?asset_owner=${currentAccount}&offset=0&limit=300`, options)
.then(response => response.json())
.then(response => console.log("collection owned by current address", response))
.catch(err => console.error(err));
useEffect(() => {
returnCollection(currentAccount)
})
The console is logging the account but when I try to pass it in the returnCollection call in useEffect() it comes back as undefined.
It seems here, you are doing something like this:
useEffect(() => {
returnCollection(currentAccount)
})
This will run after every rerender or every state change, and also initially when the component is first mounted, when currentAccount is "". Initially, current account is "", so you may not want to get the collection from "". Instead, maybe what you can do is create another state variable for the result of the returnConnection, and set the state variable to the result of the returnConnection, since you typically don't return results from useEffect, unless it's a cleanup function. Also, maybe you can check the state of currentAccount inside of the useEffect to make sure it's not "", before returning the result.

Is there a way to put react hooks in a function that's not function component?

I'm trying to create a function that is not a component nor hooks that's callable on speficic event. Let's say i have a simple function that post a data using axios and i want to use navigate after the post is successfull. Here's the example
export const authLogin = (email, password) => {
const config = {
withCredentials: true,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-CSRFToken': Cookies.get('csrftoken')
}
}
let navigate = useNavigate();
return dispatch => {
console.log('Masuk ke dalam auth file');
dispatch(authStart());
axios.post('/log_in/', {
email: email,
password: password
}, config)
.then(res => {
if (res.data.error) {
alert(res.data.error)
dispatch(authFail(res.data.error))
}
else {
const token = res.data.key;
const expirationDate = new Date(new Date().getTime() + 3600 * 1000);
localStorage.setItem('token', token);
localStorage.setItem('expirationDate', expirationDate);
dispatch(authSuccess(token));
dispatch(checkAuthTimeout(3600));
alert('login berhasil')
navigate("/", { replace: true });
}
})
.catch(err => {
alert(err);
dispatch(authFail(err))
})
}
}
I have an error that says
but when i try to change the function name with an uppercase letter, another problem occured, how do i resolve this problem?
A hook must be attached to a fiber which is directly attached to the React component tree. You CANNOT use hooks outside the component tree because React can't keep track of them (this is why hooks must always be run in the same order, and can't be conditional, because React keeps track of their state internally).
The only time you can use a hook outside of a component, is from another hook.
In short, you must be able to draw a straight line back from the hook call to React rendering the component tree. If you cannot, then it's an invalid hook call.
THE SOLUTION
...in your case - is to simply pass in the navigate function to your action as a parameter:
const MyComponent = () => {
const navigate = useNavigate();
const doSomethingHandler = () => {
dispatch(authLogin(email,password,navigate))
}
}
const authLogin = (email,password,navigate) => {
// ...do your action and call the `navigate` parameter
// when you need to
}

Handling Errors when Firebase Auth in different file

I am using Firebase's Web SDK in my Expo project, Redux is included too.
firebase.auth().signInWithEmailAndPassword() returns a Promise; I used .then() to handle the Promise and .catch() for any errors. This action is done in a separate file, not in the Login Screen because I dispatch an action in the then() clause.
Question is how would I be able to catch the error at the client's side? I can't seem to find any scenario that is similar to mine.
It's a school project :') Appreciate any help please!
LoginScreen:
I have tried calling the Toast from a useEffect and in the catch (error) { ... } clause too but to no avail. I know this way would not work but I cant seem to find any solution online.
const [loading, setLoading] = React.useState(false);
const [showAlert, setShowAlert] = React.useState(false);
const [alertMessage, setAlertMessage] = React.useState('');
const [alertStatus, setAlertStatus] = React.useState('');
const [error, setError] = React.useState(null);
const dispatch = useDispatch();
React.useEffect(() => {
if (error) {
// setAlertMessage(error);
// setShowAlert(true);
// setAlertStatus('danger');
Alert.alert('Error Occured', error, [{ text: 'Close' }]);
}
}, [error]);
const logInHandler = async () => {
try {
if (email.length === 0 || password.length === 0) {
setAlertMessage('You have empty fields!');
setShowAlert(true);
setAlertStatus('warning');
return;
}
dispatch(authActions.logIn(email, password));
setError(null);
setLoading(true);
setAlertMessage('Logging In...');
setShowAlert(true);
setAlertStatus('info');
} catch (err) {
// setAlertMessage(err.message);
// setShowAlert(true);
// setAlertStatus('danger');
setError(err.message);
setLoading(false);
}
};
return ( // i still don't get how to format properly when i paste codes here, sorry
{showAlert && (
<Toast // this is a custom toast in a separate component that i made that only shows when there is an error
message={alertMessage}
status={alertStatus}
hide={show => setShowAlert(show)}
/>
)}
);
File containing the firebase auth methods:
Throwing the error causes an Exception, but what i'm trying to achieve is to get the error message and display it using the custom Toast if there is an error from firebase
export const signUp = (email, password, name, gender) => dispatch => {
firebase
.auth()
.createUserWithEmailAndPassword(email.trim().toLowerCase(), password)
.then(res => {
firebase
.auth()
.currentUser.updateProfile({ displayName: name })
.then(() => {
dispatch({
type: STORE_USER_DATA,
id: res.user.uid,
user: res.user.displayName
});
dispatch({
type: SET_REGISTER,
isRegistering: true,
gender: gender
});
})
.catch(err => console.log('err.message', err.message));
})
.catch(err => {
let message = 'actions.signUp: An error has occured!';
let hasError =
err.code === 'auth/email-already-in-use' ||
err.code === 'auth/invalid-email' ||
err.code === 'auth/weak-password';
if (hasError) {
message = 'Invalid Credentials!';
}
throw new Error(message);
});
};
you probably need to dispatch the error instead of throwing the error - because the main usage of redux is state management so rather than throwing the error it makes more sense that you have a state that indicates if the login has failed or succeeded.
However i think it might be worth it to look at react-redux-firebase package its a clean integration between firebase and react/redux application - you could checkout authReducers, and authErrorReducers i think they might be helpful

AsyncStorage returns Promise when retrieving item

Explanation
I am trying to use AsyncStorage in order to save a Token during Login. The token is recieved from my backend as a response after the user presses the Login button. After a successful login the screen goes to the ProfileScreen where I try to retrieve the saved token.
Problem
When I try to retrieve the item in the ProfileScreen and console log it I seem to get a Promise object filled with other objects and inside there I can see my value. How do I recieve the value ? (or should I say how do I fulfill the promise :) )
Code
Utilities/AsyncStorage.js(Here I have my helper functions for store and retrieve item)
const keys = {
jwtKey: 'jwtKey'
}
const storeItem = async (key, item) => {
try {
var jsonOfItem = await AsyncStorage.setItem(key, JSON.stringify(item));
console.log('Item Stored !');
return jsonOfItem;
} catch (error) {
console.log(error.message);
}
};
const retrieveItem = async key => {
try {
const retrievedItem = await AsyncStorage.getItem(key);
const item = JSON.parse(retrievedItem);
console.log('Item Retrieved !');
return item;
} catch (error) {
console.log(error.message);
}
return;
};
LoginScreen.js(Here, after login button is pressed I recieve a response from my backend with Token)
const LoginScreen = ({componentId}) => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const loginPressed = async () => {
await axios
.post('localhost', {
email,
password,
})
.then(function(res) {
console.log(res);
storeItem(keys.jwtKey, res.data.token);
push(componentID, views.profileScreen());
})
.catch(function(error) {
console.log(error);
});
};
ProfileScreen.js(On this screen I try to retrieve the token because I will be using it)
const ProfileScreen = ({componentID}) => {
let testingAsync = retrieveItem(keys.jwtKey);
console.log(testingAsync);
The console log gives me promise object filled with other objects.
Promise{_40:0, _65:0 , _55:null, _72:null}
And inside the _55 I can find the value of the token.
Thank you for your comments! I solved it by using .then() in my ProfileScreen as #Bergi mentioned in his comment. Then after I recieved the second comment I made an async&await function in my ProfileScreen inside a useEffect to prevent it from repeating itself which solved the issue for me!

Categories

Resources