I am trying to write a function that will handle getting data to and from a server. This function takes the url to contact and uses the token to authorize itself against the server. This function is quite long. I would therefore want every other page in my react app to call this function with the needed url and then let this function handle everything else. I therefore need each page to await this function but I get "Error: Invalid hook call" no matter what I try.
This is the function that handles post requests to the server:
import React, { useEffect, useState, createRef, lazy, useContext } from "react";
import { UserContext } from "./UserContext";
import jwt_decode from "jwt-decode";
import axios from "axios";
export async function getProtectedAsset(url) {
const { user, setUser } = useContext(UserContext);
//If we do not have a token
if (user["userID"] == -1) {
return "Error: No token";
} else {
try {
//Get user data
const token = {
accessToken: user["accessToken"],
email: user["email"],
userID: user["userID"],
};
//Check if accessToken is about to expire (60s mairgain)
if (
Date.now() >=
jwt_decode(token["accessToken"])["exp"] * 1000 - 60000
) {
//Get new token
const res = await axios
.post("http://127.0.0.1:5002/refreshtoken", {
token: user["refreshToken"],
})
.then((res) => {
setUser({
userID: user["userID"],
email: user["email"],
accessToken: res.data["accessToken"],
refreshToken: user["refreshToken"],
accountType: user["accountType"],
});
})
.catch((err) => {
console.error(err);
});
}
//Our token is fresh
else {
const res = await axios
.post(url, token)
.then((promise) => {
return promise.data;
})
.catch((err) => {
console.error(err);
});
}
} catch (error) {
console.log(error);
throw err;
}
}
}
This is the page/component that I try to call this function from:
import React, { useState, useContext, useEffect, useCallback } from "react";
import { UserContext } from "../../UserContext";
import { getProtectedAsset } from "../../getProtectedAsset";
const Settings = () => {
const { user, setUser } = useContext(UserContext);
useEffect(async () => {
try {
let data = await getProtectedAsset("http://127.0.0.1:5002/mypage");
console.log(data);
} catch (error) {
console.error(error.message);
}
}, []);
return <></>;
};
export default Settings;
This gives me the 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:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
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.
I have tried everything I can imagine and read different tutorials/guides/docs but still cannot figure out the problem. Usually it is the lack of knowledge, or some thinking mistakes, but I really need help with this one!
Thank you for your help
Its because you are using useContext() hook inside getProtectedAsset() function.
Instead of using useContext inside getProtectedAsset try to pass user as parameter like url to the function.
let data = await getProtectedAsset(url, user);
Related
I would like to encapsulate the logic for a function to send push notifications so that it can be called from anywhere in the app. The push notifications API requires a jwtToken to be passed in the header. I want to call useContext on the AuthContextProvider to extract the token but the rules of hooks don't allow my function to call useContext since it isn't a React function component.
import React, { useContext } from "react";
import { AuthContext } from "#context/AuthContextProvider";
export const sendPushNotification = async function (notificationObject) {
const { authData } = useContext(AuthContext); //**<-- Fails with hooks error**
const authToken = authData.signInUserSession.idToken.jwtToken;
...
Here is the code that calls the function:
function AddToDoScreen() {
...
function handleSubmitAsync() {
...
let pushNotificationObject = {
profileIDs: [values.profileID],
title: "Push Notification Title",
body: "Push Notification Message",
};
sendPushNotification(pushNotificationObject);
I think this can be accomplished with a custom hook, but I'm not sure how. I tried starting the function name with use, but that didn't help.
Any pointers on how to implement this so I don't have to get the IdToken in each function that wants to call the sendPushNotification function?
BTW, I know you can't call Hooks inside nested functions. I tried moving the code up into the AddToDoScreen function but got the same error.
You've basically two options here:
Create a custom hook that encapsulates all the logic and returns a callback function a component can invoke.
import React, { useCallback, useContext } from "react";
import { AuthContext } from "#context/AuthContextProvider";
export const usePushNotification = () => {
const { authData } = useContext(AuthContext);
const authToken = authData.signInUserSession.idToken.jwtToken;
const sendPushNotification = useCallback(async (notificationObject) => {
... logic to use token and notification object and send push ...
}, [authToken]);
return {
sendPushNotification
};
};
function AddToDoScreen() {
const { sendPushNotification } = usePushNotification();
...
function handleSubmitAsync() {
...
const notification = {
profileIDs: [values.profileID],
title: "Push Notification Title",
body: "Push Notification Message",
};
sendPushNotification(notification);
}
...
}
Access the authToken value from the context in the React component and pass it to the callback function.
export const sendPushNotification = async ({
authToken,
notification,
}) => {
... logic to use token and notification object and send push ...
};
function AddToDoScreen() {
const { authData } = useContext(AuthContext);
const authToken = authData.signInUserSession.idToken.jwtToken;
...
function handleSubmitAsync() {
...
const notification = {
profileIDs: [values.profileID],
title: "Push Notification Title",
body: "Push Notification Message",
};
sendPushNotification({
authToken,
notification,
});
}
...
}
here's the jist of where I'm stuck (or just read the title for my question).
I have a firebase.js file where I have functions to authenticate. signinGithub, signinGoogle, signinEmail and so forth. The Firebase Auth business logic is in these functions.
I am showing errors with console.log or alert from these functions. The functions are imported into a Component and I don't know how to capture the functions result into the component by somehow setting state from this out-of-component function file.
Here's a basic example:
firebase.js
...
const signInWithGitHub = async () => {
try {
const res = await signInWithPopup(auth, githubProvider)
const user = res.user
} catch (err) {
alert(err) // ** I want to pass "err" from here to Login
// ** component by updating Logins state for a message
}
}
export {signinWithGitHub}
...
Login.jsx
import React, { useEffect, useState } from "react"
import { useAuthState } from "react-firebase-hooks/auth"
import {
auth,
signInWithGitHub
} from "../lib/firebase"
function Login() {
const [user, loading, error] = useAuthState(auth)
render(
{* Below is the method call from the imported custom firebase function *}
<button onClick={signInWithGitHub}>
Login with GitHub
</button>
)
}
...
I was thinking something like this but I can't fully resolve it in my mind:
Set state in Login.js const [message, setMessage] = useState('')
When the imported signinWithGitHub has an error message --
I'm stuck figuring out how to apply to function message to the state, any ideas?
You can create a custom function inside your Login. jsx file to call the original signInWithGitHub method with a try catch block. And more importantly, you should not use render inside a functional component. Use return to render the JSX in DOM.
firebase.js
export const signInWithGitHub = async () => {
try {
const res = await signInWithPopup(auth, githubProvider);
const user = res.user;
} catch (err) {
throw new Error(err?.message || "Unable to sign in with GitHub");
}
};
Login.jsx
import React, { useEffect, useState } from "react";
import { useAuthState } from "react-firebase-hooks/auth";
import { auth, signInWithGitHub } from "../lib/firebase";
function Login() {
const [user, loading, error] = useAuthState(auth);
const [errorMessage, setErrorMessage] = useState("");
const onLogin = async () => {
try {
await signInWithGitHub();
} catch (err) {
setErrorMessage(err);
}
};
return (
<>
<button onClick={onLogin}>Login with GitHub</button>
{!!errorMessage && <h5>{errorMessage}</h5>}
</>
);
}
I'm trying to use a request inteceptor with react-keycloak/web - however i get an array of errors when doing so.
import axios from 'axios';
import { useKeycloak } from '#react-keycloak/web';
const { keycloak } = useKeycloak();
const instance = axios.create({
baseURL: 'https://example.com/api/v1/',
timeout: 30000,
});
instance.interceptors.request.use(
(config) => {
config.headers.Authorization = `${keycloak.token}`;
return config;
},
(error) => {
return Promise.reject(error);
}
);
but i get:
React Hook "useKeycloak" cannot be called at the top level. React Hooks must be called in a React function component or a custom React Hook function react-hooks/rules-of-hooks
Now i have of course tried to create a function with for example GetToken():
function GetToken() {
const { keycloak } = useKeycloak();
return keycloak.token
}
but doing so leaves me with:
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:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
What am i doing wrong here?
You can't use react hooks out of react tree.
You can export axoios instance and import it elsewhere inside react (eg: App.js) and set interceptors there.
for example:
import axiosIns from 'api';
import { useKeycloak } from '#react-keycloak/web';
import { useEffect } from "react";
import WholeApp from "WholeApp";
const App = () => {
const { keycloak } = useKeycloak();
useEffect(() => {
axiosIns.interceptors.request.use(
(config) => {
config.headers.Authorization = `${keycloak.token}`;
return config;
},
(error) => {
return Promise.reject(error);
}
);
}, []);
return <WholeApp />;
}
I am trying to integrate firebase login in my react project while passing the idtoken into the Axios interceptor for API consumptions.
But I have noticed that my idtoken gets expired and that same old token gets passed into my API.
How can I refresh the token and pass the fresh token into my Axios interceptor?
Following is my code:
import React, { Component } from "react";
import Navigation from "./Navigation";
import firebase from "./firebaseApp";
import axios from "axios";
import "./index.css";
class App extends Component {
state = {
authenticated: undefined,
};
componentDidMount() {
firebase.auth().onAuthStateChanged((authenticated) => {
if (authenticated) {
let idTokenfinal;
firebase
.auth()
.currentUser.getIdToken()
.then((idToken) => {
idTokenfinal = idToken;
this.setState({
authenticated: true,
});
})
.catch((e) => {
alert(e);
});
firebase.auth().onIdTokenChanged(function (user) {
if (user) {
// User is signed in or token was refreshed.
user.getIdToken().then((idToken) => {
idTokenfinal = idToken;
});
}
});
axios.interceptors.request.use(
(config) => {
if (config.data && config.data.authToken) {
config.data.authToken = idTokenfinal;
}
return Promise.resolve(config);
},
function (error) {
// Do something with request error
return Promise.reject(error);
}
);
} else {
this.setState({
authenticated: false,
});
}
});
}
render() {
return this.state.authenticated !== undefined &&
this.state.authenticated !== null ? (
<Navigation authenticated={this.state.authenticated} />
) : (
<div className="loader"></div>
);
}
}
export default App;
Similar to onAuthStateChanged, there is onIdTokenChanged that gives you a new ID token whenever it's refresh. That refresh happens automatically every hour. If you have a listener here, there's no need to call getIdToken every time - just use the last value provided to the ID token listener.
If you don't want to use the listener for some reason, you can call getIdToken(true) to refresh the token each time, but that will add overhead to each call. It's better to use the value provided by the listener.
Could someone please explain what is going wrong in simple terms so I know how to fix this and can deal with it next time I encounter it.
I have looked through all related questions I could find on stackoverflow and haven't been able to fix it, if I have missed one that answers this then please link it.
I have had this error in the past but usually that was just because I had a typo (e.g. a capital instead of a lowercase) or did not import something correctly however that is not the case this time as far as I can tell.
FIRST CODE app.js
SECOND CODE interactions.js
Here is my code
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import Navbar from './Navbar'
import Web3 from 'web3';
import { connect } from 'react-redux'
// import Token from '../abis/Token.json'
import {
loadWeb3,
loadAccount,
loadToken,
loadExchange
} from '../store/interactions'
class App extends Component {
componentWillMount() {
this.loadBlockchainData(this.props.dispatch)
}
async loadBlockchainData(dispatch) {
const web3 = loadWeb3(dispatch)
const network = await web3.eth.net.getNetworkType()
const networkId = await web3.eth.net.getId()
const accounts = await loadAccount(web3, dispatch) // <<--
const token = loadToken(web3, networkId, dispatch)
loadExchange(web3, networkId, dispatch)
}
// ......................
function mapStateToProps(state) {
return {
account: accountSelector(state)
}
}
export default connect(mapStateToProps)(App);
import Web3 from 'web3'
import {
web3Loaded,
web3AccountLoaded,
tokenLoaded,
exchangeLoaded
} from './actions'
import Token from '../abis/Token.json'
import Exchange from '../abis/Exchange.json'
export const loadWeb3 = (dispatch) => {
const web3 = new Web3(Web3.givenProvider || 'http://localhost:7545')
dispatch(web3Loaded(web3))
return web3
}
export const loadAccount = async (web3, dispatch) => {
const accounts = await web3.eth.getAccounts()
const account = accounts[0]
dispatch(web3AccountLoaded(account))
return account
}
export const loadToken = async (web3, networkId, dispatch) => {
try {
const token = new web3.eth.Contract(Token.abi, Token.networks[networkId].address) // new 이거 의존성(버전) 문제 이거 조심!!!!!
dispatch(tokenLoaded(token))
return token
} catch (error) {
window.alert('Contract not deployed to the current network. Please select another network with Metamask.')
return null
}
}
export const loadExchange = async (web3, networkId, dispatch) => {
try {
const exchange = new web3.eth.Contract(Exchange.abi, Exchange.networks[networkId].address)
dispatch(exchangeLoaded(exchange))
return exchange
} catch (error) {
window.alert('Contract not deployed to the current network. Please select another network with Metamask.')
return null
}
}
i don'k now why this happening to me
but please let me know this problem if you know this issue
The problem seems to be that you do not define or import the accountSelector function anywhere.
You usually define Redux selector functions in your reducer definition files: they take the current Redux store state as argument (and optionally the connected component props) and return the value to be used in your MapStateToProps object property.
Ex.
export const accountSelector = (state) => state.account
You can read more about selectors on the dedicated Redux resources page
replace this
function mapStateToProps(state) {
return {
account: accountSelector(state)
}
}
with this
function mapStateToProps(state) {
return {
account: state.accountSelector
}
}
you are passing complete state in variable instead of accessing.
for your reference, how to access please go through official documentation for your better understanding Redux