Use react hook inside a axios config file - reactjs

i have a axios config file, and i call react hook {Auth Context} in that file with the purpose to fetch the token in react context api. but i got an error like this "React Hook 'useAuth' cannot be called at the top level. React Hooks must be called in a React function component or a custom React Hook function"
AuthContext.js
import React, { useContext, createContext, useState } from "react";
const AuthContext = createContext();
export function useAuth() {
return useContext(AuthContext);
}
export function AuthProvider({ children }) {
const [currentToken, setCurrentToken] = useState("");
const [isAuth, setIsAuth] = useState(false);
function login(token) {
setCurrentToken(token);
setIsAuth(true);
}
const value = {
login,
currentToken,
isAuth,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
and my axios config file something like this
AxiosConfig.js
import axios from "axios";
import { useAuth } from "./AuthContext";
const { currentToken } = useAuth(); //my Reeact Context
export default axios.create({
baseURL: "http://127.0.0.1:8000",
headers: {
"Content-type": "application/json",
Authorization: `Bearer ${currentToken}`,
},
});
what is the best way to achieve that goal ?
Thank you in advance

you can create an api and set the token when it's needed, since your api is the same throughout the code, this will work.
const api = axios.create({
baseURL: process.env.NEXT_PUBLIC_END_POINT,
});
export const setApiToken = (token: string) => {
api.defaults.headers.common["Authorization"] = `bearer ${token}`;
};

As several users have pointed out you cannot use a hook outside the React component. I have very similar set up in my project and store my API key in the local storage, this also adds a benefit of persisting API key.
// src/api/index.js
import axios from "axios";
import { API_URL, LOCALSTORAGE_API_KEY } from "../utils/constants";
export const signedRequest = () => {
const apiKey = localStorage.getItem(LOCALSTORAGE_API_KEY);
return axios.create({
baseURL: API_URL,
headers: apiKey ? { "Authorization": `Bearer: ${apiKey}` } : {},
});
};
export const unsignedRequest = () => {
const request = getSignedRequest();
request.defaults.headers = {};
return request;
};
Usage:
signedRequest().get<AdminDashboard.UsersResponse>("/dashboard");
And if you need to perform a request w/o Authorization you can do it:
unsignedRequest().get<AdminDashboard.UsersResponse>("/public");

Hooks were not meant to be used outside of a component, and useContext and useAuth(which uses useContext) is a hook. Here's a quote from freecodecamp:
You can not use hooks outside a component function, it is simply how they work. But, you can make a composition of hooks. React relies on an amount and order of how hooks appear in the component function. So don't even think of wrapping those calls with conditional logic of some sort.
As you read above, you are not supposed to do this in react applications.

Related

How to avoid error : Invalid hook call. Hooks can only be called inside of the body of a function component?

I have separate file where I do my get and post axios calls . So in this file I have this code
import { useSelector, useDispatch } from "react-redux";
import __ from 'lodash'
import axios from "axios";
import { getPersistedToken } from "src";
import { PROCESS_STATUS } from "src/model";
export const getAxiosQuery = (endpoint) => async () => {
const dispatch = useDispatch()
console.log(dispatch)
return await axios.get(endpoint, {
headers: {
'Authorization': 'Bearer ' + getPersistedToken(),
}
}).then((res) => {
dispatch(processStatusAction(PROCESS_STATUS.DONE))
return res
},
).catch(error => {
dispatch(processStatusAction(PROCESS_STATUS.FAIL))
});
}
Is there any way please to allow using react hooks in my function ? because I won't pass dispatch variable in parametres of this function in all my calls
Is there any way please to allow using react hooks in my function ? because I won't pass dispatch variable in parametres of this function in all my calls
No, there's no way that you can use a hook in your function. You can only use hooks in (functional) React components. Information here.
What you can do in your case, is create a custom hook (say useAxiosQuery) which retrieves the dispatch function and automatically injects it, such as:
const useAxiosQuery = (endpoint) => {
const dispatch = useDispatch();
return getAxiosQuery(endpoint, dispatch);
}

Duplicate axios calls in react

I am working on a create-react-app generated app and I have an API call to a local JSON file.
Somehow this code generates 8 API calls in a roll.
import '#css/request.scoped.css';
import { useState } from 'react';
import { getAllData } from '#api/api';
function Request() {
let [user, setUser] = useState('');
function changeUser(data) {
setUser(data);
}
getAllData().then((reply) => {
changeUser(reply.data[0].item_name);
});
return <>{user}</>;
}
export default Request;
and here is my Axios instance
import axios from "axios";
const request = axios.create({
baseURL:'./example.json',
timeout: 20000,
});
export const getAllData = () => {
return request({
method: 'get',
url: '',
});
};
Can someone tell me why this happens?
I suspect the most likely cause is that the component is being re-rendered, and currently the logic is to make the API call on every render.
You can set the logic to occur only on the first render of the component with useEffect:
import { useState, useEffect } from 'react';
// then...
useEffect(() => {
getAllData().then((reply) => {
changeUser(reply.data[0].item_name);
});
}, []);
The empty dependency array passed to useEffect means it would open happen once when the component is first loaded, not on subsequent renders. Any dependency you add to that array would cause the operation to be invoked again any time that dependency changes between renders.

React app keeps sending multiple request on its own

I'm new to react and I'm trying to build a Weather App for my project. It keeps sending the request to the API I'm using which is OpenWeatherMap API.
I'm using Axios to send the request.
Here's what my code looks like:
import React, {useEffect, useState} from 'react';
import axios from 'axios';
const App = () => {
const [ data, setData ] = useState(null);
const APIKEY = <APIKEY>;
useEffect(()=>{
window.navigator.geolocation.getCurrentPosition(
async position=>{
await axios.get(`https://api.openweathermap.org/data/2.5/onecall?lat=${position.coords.latitude}&lon=${position.coords.longitude}&appid=${APIKEY}`)
.then(response=>{
setData(response.data);
}).catch(error=>{
console.log(error);
})
}
)
}, []);
console.log(data);
return<div>hello</div>
}
export default App;
I tried using class components it doesn't work either way.

Using axios inside custom react hook

I want to create a global axios instance with an interceptor which adds a header with access token.
The access token is stored in a cookie, but I can't figure out how to access the cookie inside the interceptor as it is not a react component.
I have tried creating the axios instance in a custom hook like this:
import React, { useEffect, useState } from "react";
import { useCookies } from "react-cookie";
import axios from "axios";
function useApiClient() {
const [cookies, setCookie] = useCookies(["token"]);
const [client, setClient] = useState(null);
useEffect(() => {
setClient(
axios
.create({
baseURL: "http://localhost:8080/api/",
responseType: "json",
})
.interceptors.request.use(
(config) => {
console.log("setting up");
if (cookies["token"] != null) {
config.headers.authorization = cookies["token"];
}
},
(error) => Promise.reject(error)
)
);
}, [client]);
return client;
}
export default useApiClient;
But when I try to call this using:
const client = useApiClient();
function attemptLogin() {
const credentials = {
username: username,
password: password,
};
client
.post("/authenticate", JSON.stringify(credentials), {
headers: {
"Content-Type": "application/json",
},
}).then(....)
I get below error:
TypeError: client.post is not a function
Can anyone help?
The problem is that you are trying to use react-cookie that provides an api for working with cookies in components. It may be fine for some cases, but for the case when you need to check cookies on every request it forces putting an axios instance somewhere at the top of components hierarchy and also makes it harder to use the instance out of components (most likely you'll pass the instance to outer functions).
What I suggest is to make an instance in a separate module, and use a different tool for getting cookies like universal-cookie.
// apiClient.js
import axios from "axios";
import Cookies from "universal-cookie";
const cookies = new Cookies();
export const apiClient = axios
.create({
baseURL: "http://localhost:8080/api/",
responseType: "json"
})
.interceptors.request.use(
config => {
console.log("setting up");
const token = cookies.get("token");
if (token != null) {
config.headers.authorization = token;
}
},
error => Promise.reject(error)
);
After that, you can use this instance everywhere in the app
import React from 'react';
import { apiClient } from "./apiClient";
export default ({ name }) => {
useEffect(() => {
apiClient.get(/*...*/).then(/*...*/);
});
return <h1>Hello {name}!</h1>;
}
If you prefer to stick to hooks you could wrap a client into one easily:
// apiClient.js
export function useApiClient() { return apiClient; }
Playgroung with full example
That is maybe because useEffect in useApiClient.js is async method.
Personally I see no harm in directly assigning headers to axios object. However I see you are using axios.create so I would assume you may be making calls to multiple Service APIs/endpoints and all do not need same headers. Anyways...
Suggestions
Make useApiClient a simple class which returns instance of axios with required headers. If not, I am seriously missing the point of using useEffect here.
Behavior
In each file you file call this and all will have separate instances of axios without polluting the global axios object.
useApiClient.js
function useApiClient {
return axios
.create({
baseURL: "http://localhost:8080/api/",
responseType: "json"
})
.interceptors.request.use(
config => {
console.log("setting up");
const token = cookies.get("token");
if (token != null) {
config.headers.authorization = token;
}
},
error => Promise.reject(error)
);
}
}
export default useApiClient;
Make a Factory Class which returns axios instances based on the input parameter (to assign different headers based on parameter passed and return the instance). You may also have it as Singleton if feasible.
Hope it helps.

Standalone APIService needs access to redux or NextJS ctx for getting token

I have a standalone APIService.js which is used across my NextJS Application, it does a few things, but the main thing is setting the BASE_URL and if the user has logged in (should have a token in a cookie, or in a header cookie - this then dispatches into redux).
Basically I think I need this APIService to either; detect when there is a cookie on the machine (it does) or if its server-side rendered and get it from the header; or what I think is better - is to listen for changes to redux, and take the token from the redux store.
This is my APIService here;
import axios from 'axios';
import { Cookies } from 'react-cookie';
import { API_URL } from './constants';
const config = {
baseURL: API_URL,
};
const cookies = new Cookies();
const APIService = axios.create(config);
APIService.interceptors.request.use((request) => {
const token = cookies.get('token');
request.headers.Authorization = token ? `Bearer ${token}` : '';
return request;
});
export default APIService;
to give an example of the NextJS page usage;
I have this function in my _app.js
static async getInitialProps({ Component, ctx }) {
if (ctx.isServer) {
if (ctx.req.headers.cookie) {
const token = getCookie('token', ctx.req);
console.log('WHOAMI ', token);
ctx.store.dispatch(reauthenticate(token));
}
} else {
const { token } = ctx.store.getState().authentication;
console.log('WHOAMI2 ', token);
ctx.store.dispatch(reauthenticate(token));
}
return {
pageProps: Component.getInitialProps
? await Component.getInitialProps(ctx)
: {},
};
}
Do I need to connect() the APIService to Redux - if so how can I? I generally import the APIService into other components where I need to use my API, so i dont want to make drastic changes to it unless absolutely necessary.
I did add a context hook to the page itself to call the API client side, but this only works as a workaround for the fact I cannot get access to the header or token in the APIService as it has no context access.
So I could add a function like this to the pages, but I know its a horrid solution.
useEffect(() => {
fetchData();
}, []);
I find that if you want something to always be up-to-date with Redux, it's best to write a reselect selector for it. Something like:
import { createSelector } from 'reselect';
const getAuth = (state:AppState) => state.auth;
const getToken = createSelector(getAuth, (auth) => auth.token);
// Easy syntax for class-based services
const getSimpleService = createSelector(getToken, (token) => {
return new Service(token);
})
// Your sample APIService
const getApiService = createSelector(getToken, (token) => {
const config = {
baseURL: API_URL,
};
const APIService = axios.create(config);
APIService.interceptors.request.use((request) => {
request.headers.Authorization = token ? `Bearer ${token}` : '';
return request;
});
return APIService;
})
Now your components can get the API through connect()! My experience is with React rather than Next, but the Redux code ought to be pretty much the same. Refactoring your API service into a class might also be convenient, as you could keep all the axios setup in its constructor.
In our application, what we did is we kept the services, particularly the Authentication Service, decoupled from Redux.
What I would suggest is to keep using it in the getInitialProps instead of connecting it to redux directly. You can still use it on redux without any problems too as you only need access to the Cookies to make the login check work. It will do the job and will keep things simple.
I recommend context with hooks
Controllers
import React from 'react';
import { NextPage, NextPageContext } from 'next';
import Layout from '#layouts/index';
import { AuthStore, AuthStoreProps } from '#src/stores';
interface HomeProps {
cookie?: string;
}
const HomeController: NextPage<HomeProps> = ({ cookie }) => {
const authStore = React.useContext<AuthStoreProps>(AuthStore);
const isAuthorized = authStore.getAuthentication(cookie);
return <Layout>Home</Layout>;
};
HomeController.getInitialProps = async ({ req }: NextPageContext) => {
const cookie = req ? req.headers.cookie : '';
return { cookie };
};
export default HomeController;
Hooks
Your API Services
Opinions
You can control store values using class and context.
and then This could be reused in react or js projects.
React was concentrated on functional programming with hooks. you know.

Resources