How to use Apollo-client hook useQuery inside another function? - reactjs

I am trying to make a module for web3 authentication. For that I have a function which gets user's public address and makes a GraphqQL query to the back-end to see if it's present in the database.
export const useWeb3 = async () => {
const [ user, setUser ] = useState({})
if (!(window as any).ethereum) {
window.alert('Please install MetaMask first.');
return;
}
let web3: Web3 | undefined = undefined
if (!web3) {
try {
// Request account access if needed
await (window as any).ethereum.enable();
// We don't know window.web3 version, so we use our own instance of Web3
// with the injected provider given by MetaMask
web3 = new Web3((window as any).ethereum);
} catch (error) {
window.alert('You need to allow MetaMask.');
return [user];
}
}
const coinbase = await web3.eth.getCoinbase()
if (!coinbase) {
window.alert('Please activate MetaMask first.')
return [user]
}
console.log("COINBASE", coinbase)
const publicAddress = coinbase.toLowerCase();
// setLoading(true);
const { data } = useQuery(QUERY_USER_BY_PUBLIC_ADDRESS, {
variables: { publicAddress }
})
if(data) setUser(data)
return [user]
}
So I have a custom hook useWeb3(). I made it a hook so I could call useQuery inside it. I import useWeb3 inside the other component and when I try to use it like that:
const [user] = useWeb3()
It says Type
'Promise<{}[] | undefined>' is not an array type.ts(2461)
Welcome.component.tsx(34, 11): Did you forget to use 'await'?
The question is how can I implement the logic of taking public address and query back end with that inside a separate function so I could use it in other components?

If you just want to return the QUERY_USER_BY_PUBLIC_ADDRESS response data and aren't looking to have the results rendered as part of a React component, then you don't need to use the useQuery React hook here. Just use Apollo Client's query function directly, which returns a Promise:
...
// `client` here is your instantiated `ApolloClient` instance
const result = await client.query({
query: QUERY_USER_BY_PUBLIC_ADDRESS,
variables: { publicAddress }
});
...

Related

How to throw argument in RTK Query (queryFn)

I have queryFn query in RTK, and I need to get some data from firebase DB by element ID. But when I give this arg to queryFn like in example below, I got undefined.
and I'm calling it like this:
The reason you got undefined is because the useGetCardByIdQuery hook returns the data undefined initially. The data is going to be available after a success fetch.
As far I understand from your code, you are trying to get the cards of authorized firebase user; so you don't need to pass any id indeed since I see that you are not using the id in the queryFn.
In that case, just pass the undefined like useGetCardByIdQuery(undefined); and return the cardList.
And for better typing, you can define the builder query with <OutputType, InputType>
getCardsById: builder.query<CardList, string>({
queryFn: async (id, api, extraOptions, fetchWithBQ) => {
try {
const user = getAuth();
...
const cardList = cardSnapshot.docs.map(doc => doc.data())
return { data: cardList }
} catch (error) {
return { error }
}
},
})
Then you can call the hook in the component.
const response = useGetCardsByIdQuery(undefined);
if (response.data) {
const cards = response.data;
console.log(cards);
}

How to fire EthersJS *.on events?

I am working on a react app with redux. I did implement thunk actions to:
Create a web3modal instance
Register a ethersjs provider
Register a ethersjs signer
Everything is very basic and simple.
However whenever I try to make use of the on events (doesn't matter if provider oder contract), they simply won't fire.
I have 2 files:
walletSlice.ts which will handle all the redux action and reducer logic with #reduxjs/toolkit .
wallet-api.ts which has all the relevant functions to interact with the wallet.
The walletSlice.ts relevant part looks exactly like this:
export const connectWallet = createAsyncThunk(
'web3wallet/connectWallet',
async (arg, thunkApi) => {
const instance = await WalletAPI.registerWalletInstance();
provider = await WalletAPI.registerWalletProvider(instance);
signer = await WalletAPI.registerWalletSigner(provider);
return Promise.resolve();
}
);
The wallet-api.ts relevant parts look exactly like this:
import { ethers, providers } from 'ethers';
import Web3Modal from 'web3modal';
// get Web3Modal Instance
export async function registerWalletInstance(): Promise<Web3Modal> {
const providerOptions = {};
const web3Modal = new Web3Modal({
providerOptions,
});
const instance = await web3Modal.connect();
return Promise.resolve(instance);
}
/**
* register Wallet provider.
* Events on provider #see https://docs.ethers.io/v5/api/providers/provider/#Provider--event-methods
* Implementing the EIP-1193 Standard #see https://eips.ethereum.org/EIPS/eip-1193
*/
export async function registerWalletProvider(
instance: any
): Promise<providers.JsonRpcProvider> {
const provider = new ethers.providers.Web3Provider(instance);
// Subscribe to accounts change
provider.on('accountsChanged', (accounts: string[]) => {
console.log(accounts);
});
// Subscribe to chainId change
provider.on('chainChanged', (chainId: number) => {
console.log(chainId);
});
// Subscribe to provider connection
provider.on('connect', (info: { chainId: number }) => {
console.log(info);
});
// Subscribe to provider disconnection
provider.on('disconnect', (error: { code: number; message: string }) => {
console.log(error);
});
provider.on('error', (tx) => {
// Emitted when any error occurs
console.log({ tx });
});
return Promise.resolve(provider);
}
// register Wallet signer.
export async function registerWalletSigner(
provider: providers.JsonRpcProvider
): Promise<providers.JsonRpcSigner> {
const signer = provider.getSigner();
return Promise.resolve(signer);
}
None of the provider.on() events will fire. I've tried to change networks from rinkeby to polygon or mainnet, but nothing happens. When I disconnect from the site, nothing happens. It is the same with all provider events as shown in wallet-api.ts. I did try the same approach with another file called contract-api.ts. However the contract events won't fire either.
I tried to use the provider.on() events with useEffect() or useCallback(). Moved the code to a standalone tsx. But nothing happened.
await web3Modal.connect() already returns a provider;
try to use that one instead of new ethers.providers.Web3Provider(instance);

Return data from Async function React Native Redux

I am having trouble with accessing the data after fetching it with SecureStore in Expo for react-native.
Here is the simple code:
const infoofuser = SecureStore.getItemAsync('userInfo').then(value =>
console.log(`this is the vlaue from infouser: ${value}`),
);
console.log(`infoouser: ${JSON.stringify(infoofuser)}`);
the first infoofuser constant definition returns the object of the intended data.
console.log(`infoouser: ${JSON.stringify(infoofuser)}`);
however returns {"_U":0,"_V":0,"_W":null,"_X":null} which U understand is a promise. I would like to simply get the data that comes from the SecureStore call and use it to set my initialState in redux.
const infoofuser = SecureStore.getItemAsync('userInfo').then(value =>
value
);
this does not work either to access the data
You can use async method using async/await. Try this:
const userInfo = useSelector(state => state.userInfo);
const getData = async () => {
try {
const infoofuser = await SecureStore.getItemAsync('userInfo');
console.log('infoofuser:', infoofuser)
/// strore on redux
} catch (err) {
// handle error
}
}
useEffect(() => {
getData()
}, [])
if (!userInfo) return null
//render something else
You can check the Expo Secure Store docs for reference.

How to setup a function which gets app settings and sets it as localStorage before the page loads. (next.js)

I've been working on a Next.JS web application for the past couple of days but I've reached a problem. The app has an API call (/api/settings) which returns some settings about the application from the database. Currently, I have a function which returns these settings and access to the first component:
App.getInitialProps = async () => {
const settingsRequest = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/settings`
);
const settingsResponse = await settingsRequest.json();
return { settings: settingsResponse };
};
This does work and I am able to pass in settings to components but there are two problems with this:
I need to nest the prop through many components to reach the components that I need
This request runs every time a page is reloaded/changed
Essentially, I need to create a system that does this:
runs a function in the _app.tsx getInitialProps to check if the data is already in localStorage, if not make the API request and update localStorage
have the localStorage value accessible from a custom hook.
Right now the problem with this is that I do not have access to localStorage from the app.tsx getInitialProps. So if anyone has an alternative to run this function before any of the page loads, please let me know.
Thanks!
I found a solution, it might be a janky solution but I managed to get it working and it might be useful for people trying to achieve something similar:
First we need to create a "manager" for the settings:
export const checkIfSettingsArePresent = () => {
const settings = localStorage.getItem("app_settings");
if (settings) return true;
return false;
};
export const getDataAndUpdateLocalStorage = async () => {
const r = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/settings`);
const response = await r.json();
localStorage.setItem("app_settings", JSON.stringify(response));
};
With that created we can add a UseEffect hook combined with a useState hook that runs our function.
const [doneFirst, setDoneFirst] = useState<boolean>(false);
useEffect(() => {
const settingsPreset = checkIfSettingsArePresent();
if (performance.navigation.type != 1)
if (settingsPreset) return setDoneFirst(true);
const getData = async () => {
await getDataAndUpdateLocalStorage();
setDoneFirst(true);
};
getData();
}, []);
//any other logic
if (!doneFirst) {
return null;
}
The final if statement makes sure to not run anything else before the function.
Now, whenever you hot-reload the page, you will see that the localStorage app_settings is updated/created with the values from the API.
However, to access this more simply from other parts of the app, I created a hook:
import { SettingsType } from "#sharex-server/common";
export default function useSettings() {
const settings = localStorage.getItem("app_settings") || {
name: "ShareX Media Server",
};
//#ts-ignore
return JSON.parse(settings) as SettingsType;
}
Now I can import useSettings from any function and have access to my settings.

React: useContext vs variables to store cache

I have the following code which I implemented caching for my users:
import { IUser } from "../../context/AuthContext";
export const usersCache = {};
export const fetchUserFromID = async (id: number): Promise<IUser> => {
try {
const res = await fetch("users.json");
const users = await res.json();
Object.keys(users).forEach((userKey) => {
const currentUser = users[userKey];
if (!currentUser) {
console.warn(`Found null user: ${userKey}`);
return;
}
usersCache[users[userKey].id] = currentUser;
});
const user = usersCache[id];
return user;
} catch (e) {
console.error(`Failed to fetch user from ID: ${id}`, e);
throw Error("Unable to fetch the selected user.");
}
};
As you can see, the variable userCache stores all the users.
It works fine and I can access this variable from all my components.
I decided that I want to "notify" all my components that the userCache has changed, and I had to move this logic to a react Context and consume it with useContext.
So the questions are:
How I can set the userCache context although the above code is not a react component? (it's just a typescript file I called 'UserService')?
I can't do:
export const fetchUserFromID = async (id: number): Promise<IUser> => {
const { setUserCache } = useContext(MembersContext);
...
}
React Hook "useContext" is called in function "fetchUserFromID" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. (react-hooks/rules-of-hooks)eslint
Is there a reason to prefer Context over variable as above altough the data is not subject to change frequently?
Thanks.
How I can set the userCache context although the above code is not a react component? (it's just a typescript file I called 'UserService')?
You need to declare your context value somewhere in a component in order to use it.
const MembersContext = React.createContext({}); // initial value here
function App() {
const [users, setUsers] = useState({});
const fetchUserFromID = async (id: number) => {
/* ... */
// This will call `setUsers`
/* ... */
}
return (
<MembersContext.Provider value={users}>
{/* your app components, `fetchUserFromId` is passed down */}
</MembersContext.Provider>
);
}
function SomeComponent() {
const users = useContext(MembersContext);
return (/* you can use `users` here */);
}
Is there a reason to prefer Context over variable as above altough the data is not subject to change frequently?
If you need your components to update when the data changes you have to go with either :
a context
a state passed down through props
a redux state
More info here on how to use Contexts.

Resources