How to avoid circular import (dependency) in JS? - javascript

I have a simple example where I have a circular dependency issue. What can be done to avoid this importing loop?
store.js
import { fetchApi } from "./api";
class Store {
auth = "zzzzz";
fetch = () => fetchApi();
}
const store = new Store();
export default store;
api.js
import client from "./client";
export const fetchApi = () => client("/test");
client.js
import store from "./store";
const client = (endpoint) => {
const config = {
method: "GET",
headers: {
Authorization: `Bearer ${store.auth}`
}
};
return window.fetch(endpoint, config);
};
export default client;
With this code, I have the following situation:
store is importing api
api is importing client
client is importing store
store -> api -> client -> store
Happy to hear suggestions or see examples on how can I avoid this pattern. 🙏🏻

Related

React Native, passing locale parameter from i18next to Axios config file gives Invalid hook call error

I'm trying to implement a locale parameter into my axiosConfig file:
import axios from "axios";
const instance = axios.create({
baseURL: "http://10.0.2.2:8000/api",
timeout: 2000,
});
instance.defaults.headers.common["locale"] = "en";
export default instance;
On each screen I make my get and post calls on screens as such:
axiosConfig
.get("/someroute")
.then((response) => {
//console.log(response.data);
})
.catch((error) => {
console.log(error.message);
});
The above code works as intended. Now I want to pass a "locale" parameter into all of my axios calls. This parameter will come from app locale (i use i18next). When I implement it as below, it throws an invalid hook error.
import axios from "axios";
import { useTranslation } from "react-i18next";
const { i18n } = useTranslation();
const instance = axios.create({
baseURL: "http://10.0.2.2:8000/api",
timeout: 2000,
});
instance.defaults.headers.common["locale"] = i18n.language;
export default instance;
What would be the correct way to set the locale header in my configuration?
You are getting this error because a hook should be called in a React Component or inside another hook. See Rules of Hooks. And here is what you could do for example:
Transform the file where you are setting the axios instance to a hook:
import axios from "axios";
import { useTranslation } from "react-i18next";
const useAxiosInstance = ()=>{
const { i18n } = useTranslation();
const instance = axios.create({
baseURL: "http://10.0.2.2:8000/api",
timeout: 2000,
});
instance.defaults.headers.common["locale"] = i18n.language;
return instance;
}
export default useAxiosInstance;
You include the hook at the top of the file where you are using the axios config and use it as an example this way:
import {useEffect} from "react";
import useAxiosConfig from "./path";
const AxiosConsumer = ()=>{
const axiosConfig = useAxiosConfig();
useEffect(()=>{
axiosConfig
.get("/someroute")
.then((response) => {
//console.log(response.data);
})
.catch((error) => {
console.log(error.message);
});
},[axiosConfig]);
return <></>
}

React Native apisauce can't connect to server network

I'm learning react native and therefore I'm using an api package by the name of "apisauce" version ^2.1.2.
when backend be called with postman, I receive an array of data and when it be called with frontend application, I receive "Network_Error"
My question is what I'm doing wrong?
client.js :
import { create } from "apisauce";
const apiClient = create({
baseURL: "http://127.0.0.1:9000/api",
});
export default apiClient;
listings.js:
import client from "./client";
const endpoint = "/listings";
const getListings = () => client.get(endpoint);
export default { getListings };
App.js
import React, { useEffect } from "react";
import listingsApi from "./app/api/listings";
function App() {
const loadData = async () => {
const response = await listingsApi.getListings();
console.log(response);
};
useEffect(() => {
loadData();
}, []);
return (
<Screen>
</Screen>
);
}
export default App;
And here is the error when I do a console.log:
I have found a solution where instead to set the baseURL to backend URL, I had to set the baseURL to the ip-address of my computer.
information had been found at

How to use the global data from React inside Apollo client's initialization?

When it comes to state centralization I know how to use the context api and Redux. But to recover that state we always have to be inside a react component.
What is the best strategy to access a global state/variable inside a common function that is not inside a react component?
In the environment variables is not an option because this value is changed after the application runs. And I didn't want to put in cookies or local storage for security reasons.
Index.ts
import React from 'react';
import ReactDOM from 'react-dom';
import { ApolloProvider } from 'react-apollo';
import apolloClient from './services/apollo';
import { PersonalTokenProvider } from './providers/personal-token';
import './index.css';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<PersonalTokenProvider>
<ApolloProvider client={apolloClient}>
<App />
</ApolloProvider>
</PersonalTokenProvider>
</React.StrictMode>,
document.getElementById('root'),
);
PresonalToken context provider
import React, { useState } from 'react';
interface ProviderProps {
children: JSX.Element[] | JSX.Element;
}
export const PersonalTokenContext = React.createContext({});
export const PersonalTokenProvider: React.FC<ProviderProps> = (
props: ProviderProps,
) => {
const [token, setToken] = useState<string | null>(null);
const { children } = props;
return (
<PersonalTokenContext.Provider value={{ token, setToken }}>
{children}
</PersonalTokenContext.Provider>
);
};
apollo client config
import { useContext } from 'react';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { PersonalTokenContext } from '../providers/personal-token';
//cant do this
const {token} = useContext(PersonalTokenContext)
const httpLink = new HttpLink({
uri: 'https://api.github.com/graphql',
headers: {
authorization: `Bearer ${token}`,
},
});
const client = new ApolloClient({
link: httpLink,
cache: new InMemoryCache(),
});
export default client;
Pure React Apollo client initialization
There are multiple ways to simulate a singleton to manage the Apollo client from within React. Here's one way using useRef to always have the latest token when making GraphQL queries and useMemo to only create the client once.
import {
ApolloClient,
createHttpLink,
InMemoryCache,
ApolloProvider
} from '#apollo/client';
import { setContext } from '#apollo/client/link/context';
// The name here doesn't really matters.
export default function CustomApolloProvider(props) {
const { token } = useContext(PersonalTokenContext);
const tokenRef = useRef();
// Whenever the token changes, the component re-renders, thus updating the ref.
tokenRef.current = token;
// Ensure that the client is only created once.
const client = useMemo(() => {
const authLink = setContext((_, { headers }) => ({
headers: {
...headers,
authorization: tokenRef.current ? `Bearer ${tokenRef.current}` : '',
}
}));
const httpLink = createHttpLink({
uri: 'https://api.github.com/graphql',
});
return new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
});
}, [])
return <ApolloProvider client={client} {...props} />;
}
Then in the app:
<PersonalTokenProvider>
<CustomApolloProvider>
<App />
</CustomApolloProvider>
</PersonalTokenProvider>
Pros:
Totally inside of React, which means it could use other hooks and data that changes from different places, like the locale code from the translation lib, etc.
One client per mounted application, which means, if the application needs to be unmounted, this solution would ensure proper cleanup.
Easy to add unit/integration tests
Cons:
A little more complex to put in place.
If not properly setup, multiple Apollo clients could end up being created, losing the previous cache, etc.
Using localStorage
The Apollo documentation suggests using the local storage to manage the authentication token.
import { ApolloClient, createHttpLink, InMemoryCache } from '#apollo/client';
import { setContext } from '#apollo/client/link/context';
const httpLink = createHttpLink({
uri: '/graphql',
});
const authLink = setContext((_, { headers }) => {
// get the authentication token from local storage if it exists
const token = localStorage.getItem('token');
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
}
}
});
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
});
Pros:
Simple to add to your existing implementation
There's ever only one client created for the entire lifetime of the app
The local storage is a good place to store global data across tabs, refresh, etc.
Cons:
Lives outside of React, so the app wouldn't re-render when the token changes, etc.
Could be harder/complex to unit test.
Using module scoped variable
Using a simple variable at the root of the module would be enough, you wouldn't even need the token context anymore.
import {
ApolloClient,
createHttpLink,
InMemoryCache,
makeVar
} from '#apollo/client';
import { setContext } from '#apollo/client/link/context';
// module scoped var for the token:
let token;
// custom module setter:
export const setToken = (newToken) => token = newToken;
const httpLink = createHttpLink({
uri: '/graphql',
});
// Apollo link middleware gets called for every query.
const authLink = setContext((_, { headers }) => ({
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
}
}
));
export const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
});
Pros:
Simple to add to your existing implementation
There's ever only one client created for the entire lifetime of the app
Cons:
Lives outside of React, so the app wouldn't re-render when the token changes, etc.
Could be harder/complex to unit test
Lost when the user refreshes the page, or closes the app.
Reactive vars to manage the token
juanireyes suggested Apollo Reactive variables, but they're meant for a particular use-case, which is totally unnecessary to manage the token globally like we want here. It is similar to the module scope variable suggestion above, but with extra steps.
If you are trying to use Apollo I would personally encourage you to use the updated library: #apollo/client. Then you can use Reactive Variables to access the state from multiple places. Then you can try in your provider file something like this to access the token variable:
import React, { useState } from 'react';
import { makeVar } from '#apollo/client';
interface ProviderProps {
children: JSX.Element[] | JSX.Element;
}
export const tokenVar = makeVar<string | null>(null);
export const PersonalTokenContext = React.createContext({});
export const PersonalTokenProvider: React.FC<ProviderProps> = (
props: ProviderProps,
) => {
const [token, setToken] = useState<string | null>(null);
useEffect(() => {
tokenVar(token)
}, [token]);
const { children } = props;
return (
<PersonalTokenContext.Provider value={{ token, setToken }}>
{children}
</PersonalTokenContext.Provider>
);
};
And finally you can access the token value from everywhere calling tokenVar() or using the useReactiveVar hook.
You can access the content of the Redux store from outside of a component. I know two ways of doing so:
getState
Import the store from the file where you declare it, and access the whole state with the getState method:
import { store } from '../myReduxConfig.js';
const myFunc = () => {
const reduxData = store.getState();
}
subscribe
If you need the function to run again on redux store changes, import the store from the file where you declare it, and subscribe your function to it:
import { store } from '../myReduxConfig.js';
store.subscribe(myFunc);
const myFunc = () => {
const reduxData = store.getState();
}

Axios mock adapter giving error 'Error: Request failed with status code 404'

I have a component in which I am making an API call on mount
import * as React from 'react';
import axios from 'axios';
import './index.scss';
// import {axiosInstance} from '../../mocks/index';
// axios(client)
// axiosInstance(axios);
const FeatureTable = () => {
React.useEffect(() => {
axios.get("http://localhost:8080/users").then(function (response: any) {
console.log(response.data);
});
}, []);
return (
<div className="container">
</div>
)
}
export default FeatureTable;
And I have setup my mock adapter in a different folder like this
const Axios = require("axios");
const MockAdapter = require("axios-mock-adapter");
import featureTable from './table';
export const axiosInstance = Axios.create();
const mock = new MockAdapter(axiosInstance, { delayResponse: 1000, onNoMatch: "throwException" });
featureTable(mock);
In my table file, I have this code -
const users = [{ id: 1, name: "John Smith" }];
const featureTable = (mock: any) => {
mock.onGet("http://localhost:8080/users").reply(200, users);
}
export default featureTable;
Upon running the code, I get 404 error not found. Please help with the fix.
First of all: you have to use the same axios instance with both places: to setup the mock response and to make the actual call. When I want to mock some API I usually create and export the axios instance in a separate file, and just import it where I need it. like so:
// dataAPI.js
import axios from 'axios';
export const dataAPI = axios.create(); // general settings like baseURL can be set here
// FeatureTable.js
import React, { useEffect } from 'react';
import { dataAPI } from './dataAPI';
export const FeatureTable = () => {
useEffect(() => {
dataAPI.get('http://localhost:8080/users').then(something);
}, []);
return (<div>something</div>);
};
// mock.js
import { dataAPI } from './dataAPI';
import MockAdapter from 'axios-mock-adapter';
import { users } from './mockData.js'
const mock = new MockAdapter(dataAPI);
mock.onGet('http://localhost:8080/users').reply(200, users);
I want to point out axios-response-mock as an alternative to axios-mock-adapter. Disclaimer: I'm the author of that library. I was a bit frustrated by mock-adapter because I didn't find the API intuitive and it has limitations (e.g. can't match URL params in conjunction with POST requests) and that it would not let unmatched requests pass-through by default.
In your table file, you have to pass the relative path, not the absolute one.
const users = [{ id: 1, name: "John Smith" }];
const featureTable = (mock: any) => {
mock.onGet("/users").reply(200, users);
}
export default featureTable;

Why I could connect API via axios?

I created react app with Typescript and I made a request via axios.
Here is my original code.
// /src/axios.js
import axios from 'axios';
const instance = axios.create({
baseURL:"https://api.baseurl.org",
});
export default instance;
// /src/component.js
import React from "react";
import axios from "./axios";
...
const Component = ({ fetchUrl }) => {
async function fetchData() {
const request = await axios.get(fetchUrl);
}
...
};
...
I could get responses correctly, but don't know why I could make a request.
In axios.js file, I export instance, not axios.
In component.js file, I import axios.
I think I should import instance in component.js, that is, modify the file like this :
// /src/component.js modified
import React from "react";
import instance from "./axios";
...
const Component = ({ fetchUrl }) => {
async function fetchData() {
const request = await instance.get(fetchUrl);
}
...
};
...
I could get the same result correctly.
Two ways of using axios instance made correct results.
Why I could connect API with the original code?
In axios.js file, I export instance, not axios. In component.js file, I import axios.
You're using a default export, not a named export. The name you assign to it is completely up to the module doing the importing.
Consider:
const foo = 123;
export default foo;
export const bar = 456;
To import that you say:
import whatever_you_want_to_call_it, { bar as anything_you_like } from './export.mjs';
bar is a named export so you have to specify that it is bar you want to import (but giving it a different name is optional). The default export has to be given a name, but which name is entirely up to you.

Categories