make redux toolkit query inside useEffect - api

I am receiving a cat prop and want to fetch data from api whenever cat is changed using RTK query. But it's getting error the following error:
React Hook "useGetProductsQuery" cannot be called inside a callback".
How to make a RTK query whenever cat is changed?
const Products = ({cat,filter,sort}) => {
const [products, setproducts] = useState([])
const [filters, setFilters] = useState([])
useEffect(()=>{
const {data,isError,isLoading}=useGetProductsQuery()
console.log(data,isError,isLoading)
},[cat])
}
import { createApi, fetchBaseQuery }
from'#reduxjs/toolkit/query/react';
const baseUrl=process.env.REACT_APP_ECOMMERCE_API_URL
const createRequest = (url) => ({ url });
export const ecommApi = createApi({
reducerPath: 'ecommApi',
baseQuery: fetchBaseQuery({baseUrl}),
endpoints: (builder) => ({
getProducts: builder.query({
query: () => createRequest("/api/v1/products")
}),
})
})
export const{useGetProductsQuery}=ecommApi;

Why not just pass cat as an argument to the useQuery hook without the useEffect?
That will make a new request every time cat changes.
const {data,isError,isLoading}=useGetProductsQuery(cat)

As an addition to phry's answer:
If "cat" might be undefined use the skip parameter for conditional fetching:
const {data,isError,isLoading} = useGetProductsQuery(cat, {skip: !cat})
See https://redux-toolkit.js.org/rtk-query/usage/conditional-fetching

Related

React native UseEffect and async issue

I'm trying to get a StoreKey from firestore (v9), and put it inside another collection of DB as a path.
for example, get storeKey (132, for example) and put inside
collection(db, 'store', storeKey, 'coffeeDB') to access specific sub collection. I put two function (1: getData (storeKey), 2: access to sub collection) into UseEffect so that it can run when it's mounted.
However, I found UseEffect runs twice, initial storeKey shows Array [], and the next run gets proper value which is 132. So, I'm having an error due to the first run.
I guess it's because the second function inside UseEffect does not wait for getData function to watch the data, but not too sure.
How can I resolve this issue??
const getData = async(setStoreKey, setName) => {
console.log('xxxx')
const auth = getAuth();
const user = auth.currentUser;
if(user !== null){
const email = user.email;
const UserInfo = await getDoc(doc(db, 'users', email));
if(UserInfo.exists()){
setStoreKey(UserInfo.data().storeKey)
setName(UserInfo.data().name);
}
else{
console.log('None')
}
return
}
}
T
const StockScreen = ({ navigation }) => {
const [storeKey, setStoreKey] = useState([]);
const [userName, setName] = useState([]);
const [coffeeStock, setCoffeeStock] = useState([]);
useEffect(() => {
getData(setStoreKey, setName);
const unsub = onSnapshot(collection(db, 'Store', storeKey, 'coffeeDB'), (snapshot) => {
setCoffeeStock(snapshot.docs.map((doc) => ({
id: doc.id,
data: doc.data(),
number: doc.data(),
})));
});
return unsub;
}, [storeKey]);
Just remove dependency from second argument in useEffect and pass blank array
useEffect(() => {
// your code
}, []);
It will run only once when your component is loaded. It is similar to componentDidMount of class component.
The reason your useEffect run twice is because your storeKey state changing in getData(setStoreKey, setName) function. So what you can do here if you want to call getData() function once is to declare it on a separate useEffect function like:
useEffect(() => {
getData(setStoreKey, setName); //call your getData function once
}, []);
And what I see is you need to update StoreKey every time for the unsub listener so with that above useEffect call another useEffect whenever the StoreKey dependency change like:
useEffect(() => {
getData(setStoreKey, setName); //call your getData function once
}, []);
useEffect(() => { //another useEffect whenever storeKey changes
const unsub = onSnapshot(collection(db, 'Store', storeKey, 'coffeeDB'), (snapshot) => {
setCoffeeStock(snapshot.docs.map((doc) => ({
id: doc.id,
data: doc.data(),
number: doc.data(),
})));
});
return unsub;
}, [storeKey]);
Hope this works for you.

How to access the state using Redux RTK in react native app

I'm using Redux RTK query in my react native project and I can't figure out how to access the state without firing the query each time.
I created an api with my customBaseQuery
const emptySplitApi = createApi({
reducerPath: 'api',
baseQuery: customBaseQuery(),
endpoints: () => ({})
})
I then injected my endpoints.
export const userApi = emptySplitApi.injectEndpoints({
endpoints: (builder) => ({
getUser: builder.query<User, GetUserInput>({
query: (options) => ({
query: getUserQuery,
options: options
})
})
}),
overrideExisting: true
})
And configured the store:
const persistConfig = {
key: 'root',
storage: AsyncStorage
}
const reducers = combineReducers({
[emptySplitApi.reducerPath]: emptySplitApi.reducer
})
const persistedReducer = persistReducer(persistConfig, reducers)
export const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: false }).concat(emptySplitApi.middleware)
})
When I open the app I call the getUser API using the redux-generated hook. This works and I get the expected response.
const { data } = useGetUserQuery(input)
Now, I need to access the user data in another screen of my app without calling the API again (redux should have cached/persisted the data) but I can't manage to access the state.
I tried the following:
const { getUser } = useAppSelector((state) => state.api.queries)
but getUser is always undefined. However if I print the whole state I can see the data.
How can I access the state from anywhere in the app without calling the API again?
Just use your useGetUserQuery(yourOptions) everywhere. It will fire only one request to the server, no matter how many times you use it, and the data will be available everywhere.

Async custom hook from within useEffect

When kept in the component body, the following code works fine. Inside useEffect, it checks the asyncstorage and dispatches an action (the function is longer but other checks/dispatches in the function are of the same kind - check asyncstorage and if value exists, dispatch an action)
useEffect(() => {
const getSettings = async () => {
const aSet = await AsyncStorage.getItem('aSet');
if (aSet) {
dispatch(setASet(true));
}
};
getSettings();
}, [dispatch]);
I'm trying to move it to a custom hook but am having problems. The custom hook is:
const useGetUserSettings = () => {
const dispatch = useDispatch();
useEffect(() => {
const getSettings = async () => {
const aSet = await AsyncStorage.getItem('aSet');
if (aSet) {
dispatch(setASet(true));
}
};
getSettings();
}, [dispatch]);
};
export default useGetUserSettings;
Then in the component where I want to call the above, I do:
import useGetUserSettings from './hooks/useGetUserSettings';
...
const getUserSettings = useGetUserSettings();
...
useEffect(() => {
getUserSettings();
}, [getUserSettings])
It returns an error:
getUserSettings is not a function. (In 'getUserSettings()', 'getUserSettings' is undefined
I've been reading rules of hooks and browsing examples on the internet but I can get it working. I've got ESlint set up so it'd show if there were an invalid path to the hook.
Try the following.
useEffect(() => {
if (!getUserSettings) return;
getUserSettings();
}, [getUserSettings]);
The hook doesn't return anything, so it's not surprising that the return value is undefined ;)

can't get data from server to NuxtJS Store

this is my code :
export const state = () => ({
products: []
});
export const getters = {
getProducts: state => {
return state.products;
}
};
export const mutations = {
SET_IP: (state, payload) => {
state.products = payload;
}
};
export const actions = () => ({
async getIP({ commit }) {
const ip = await this.$axios.$get("http://localhost:8080/products");
commit("SET_IP", ip);
}
});
the server is working nicely but i just can't get the data into the store
First of all, I highly recommend you rename your action and mutation to something like getProducts and SET_PRODUCTS instead of ip. Also make sure you change the variable name inside the action. While this doesn't change any functionality, it makes your code easier to read.
Second, maybe add a console.log(ip) right after you define the const in the action and see if you're getting the data you want in there. In most cases you're going to want to assign ip.data to your variable.
Lastly, make sure you're calling the action somewhere in the code.
You should do it like this:
this.$store.dispatch('getIP'); // Using your current name
this.$store.dispatch('getProducts'); // Using my recommended name

how to handle failed silent auth error in auth0

I followed spa react quick start guide and it worked fine for more than a month. Recently i had this error and it is logged on auth0 as 'failed silent error' with no further information. I have been told that it is because of the browsers cookie updates and recommended to use new beta release of auth0-spa-js and change cache location to local storage. And it didn't work either.
The code is as follows:
auth_config.json:
{
"domain": "dev.........eu.auth0.com",
"clientId": "....eEKkQ.............",
"redirect_uri": "https://localhost:8080",
"audience": "https://.......herokuapp.com/v1/....",
"cacheLocation": "localstorage"
}
and
react-auth0-wrapper.js:
import React, { useState, useEffect, useContext } from "react";
import createAuth0Client from "#auth0/auth0-spa-js";
const DEFAULT_REDIRECT_CALLBACK = () =>
window.history.replaceState({}, document.title, window.location.pathname);
export const Auth0Context = React.createContext();
export const useAuth0 = () => useContext(Auth0Context);
export const Auth0Provider = ({
children,
onRedirectCallback = DEFAULT_REDIRECT_CALLBACK,
...initOptions
}) => {
const [isAuthenticated, setIsAuthenticated] = useState();
const [user, setUser] = useState();
const [auth0Client, setAuth0] = useState();
const [loading, setLoading] = useState(true);
const [popupOpen, setPopupOpen] = useState(false);
useEffect(() => {
const initAuth0 = async () => {
const auth0FromHook = await createAuth0Client(initOptions);
setAuth0(auth0FromHook);
if (window.location.search.includes("code=")) {
const { appState } = await auth0FromHook.handleRedirectCallback();
onRedirectCallback(appState);
}
const isAuthenticated = await auth0FromHook.isAuthenticated();
setIsAuthenticated(isAuthenticated);
if (isAuthenticated) {
const user = await auth0FromHook.getUser();
setUser(user);
}
setLoading(false);
};
initAuth0();
// eslint-disable-next-line
}, []);
const loginWithPopup = async (params = {}) => {
setPopupOpen(true);
try {
await auth0Client.loginWithPopup(params);
} catch (error) {
console.error(error);
} finally {
setPopupOpen(false);
}
const user = await auth0Client.getUser();
setUser(user);
setIsAuthenticated(true);
};
const handleRedirectCallback = async () => {
setLoading(true);
await auth0Client.handleRedirectCallback();
const user = await auth0Client.getUser();
setLoading(false);
setIsAuthenticated(true);
setUser(user);
};
return (
<Auth0Context.Provider
value={{
isAuthenticated,
user,
loading,
popupOpen,
loginWithPopup,
handleRedirectCallback,
getIdTokenClaims: (...p) => auth0Client.getIdTokenClaims(...p),
loginWithRedirect: (...p) => auth0Client.loginWithRedirect(...p),
getTokenSilently: (...p) => auth0Client.getTokenSilently(...p),
getTokenWithPopup: (...p) => auth0Client.getTokenWithPopup(...p),
logout: (...p) => auth0Client.logout(...p)
}}
>
{children}
</Auth0Context.Provider>
);
};
What is wrong with this code, any help appreciated. Or i can use a different method, i just followed the docs, it doesn't matter as long as it authenticates.
Thanks
I know this has been hanging around for a bit, but i was running into a similar issue.
As I understand it the createAuth0Client helper factory runs the getTokenSilently function by default as part of the set up to re-authenticate users every browser refresh. The problem i was having was that the call to getTokenSilently was erroring, meaning that auth0FromHook was never set and the auth0client never set in state. Because auth0client was undefined, it was then impossible to call loginwithredirect, which is the behaviour i wanted to achieve.
Basically i wanted it to auth silently, but if it failed, send to the log in screen, but that's impossible because the auth0client was undefined, resulting in a cannot call loginwithredirect of undefined error. It seems that (sadly) in the current stable version of the #auth0/auth0-spa-js library (1.6.5 at time of writing) there is no way to bypass getTokenSilently when initialising the client. However in the current beta (1.7.0-beta.5) (Here is a list of versions) they have exposed the Auth0Client class itself, so if you want to move to that version the code could be tweaked with something like....
initAuth0().catch( e => {
const newClient = new Auth0Client(initOptions);
setAuth(newClient);
})
and then in any protected components you can check the loading is finished and if isAuthenticated is still falsey, you should be able to redirect to login despite an error occurring during the getSilentToken.
== NON BETA OPTION
The alternative in the current api would be to perhaps set max_age to 0 or 1 in the initOptions, to force a re-login, and maybe setting prompt to "login" on the second attempt to initialize the authClient