I got 'Too many re-renders' while converting my axios codes to react-query - reactjs

It seems like react-query is a quiet popular so, I trying to add react-query to my exist codes.
the code below is the exist codes. it uses hooks (useEffect & useState), axios and returns response data.
import { useState, useEffect } from 'react';
import { apiProvider } from 'services/modules/provider';
import { useLoading } from 'components/Loading/Loading';
export const useCommonApi = (url: string, params?: any) => {
const [_, setLoading] = useLoading();
const [State, setState] = useState<any>();
useEffect(() => {
try {
const getState = async () => {
const result: any = await apiProvider.get('common/' + url, params);
let resultData = result.data || [];
if (url === 'available_countries') {
resultData = resultData.map((o: any) => {
return { value: o.id, label: o.name };
});
}
setState([...resultData]);
return resultData;
};
getState();
} catch (e) {
console.error(e);
}
}, []);
return State;
};
Here is the my new codes for react-query. I am trying to convert code above into react-query as below.
import { useState, useEffect } from 'react';
import { apiProvider } from 'services/modules/provider';
import { useLoading } from 'components/Loading/Loading';
import axios from 'axios';
import { useQuery } from 'react-query';
export const useCommonApi_adv = (url: string, params?: any) => {
const [_, setLoading] = useLoading();
const [State, setState] = useState<any>();
const { isLoading, error, data } = useQuery('fetchCommon', () =>
axios('/api/v1/admin/common/' + url).then( (res) :any => {
return res.data
})
)
if (isLoading) return 'Loading...'
let resultData = data.data || [];
if (url === 'available_countries') {
resultData = resultData.map((o: any) => {
return { value: o.id, label: o.name };
});
}
setState([...resultData]);
return State;
};
the my new codes(react-query) prints "too many render" when it is executed.
What did I wrong with it? any help please

You are calling your state update function setState outside of an useEffect. This will run on the first render, update the state, which in turn triggers a rerender, update the state again and you end up in an endless loop. You probably want to wrap that logic into useEffect and only run it if data changes.
import { useState, useEffect } from 'react';
import { apiProvider } from 'services/modules/provider';
import { useLoading } from 'components/Loading/Loading';
import axios from 'axios';
import { useQuery } from 'react-query';
export const useCommonApi_adv = (url: string, params?: any) => {
const [_, setLoading] = useLoading();
const [State, setState] = useState<any>();
const { isLoading, error, data } = useQuery('fetchCommon', () =>
axios('/api/v1/admin/common/' + url).then( (res) :any => {
return res.data
})
)
useEffect(() => {
let resultData = data.data || [];
if (url === 'available_countries') {
resultData = resultData.map((o: any) => {
return { value: o.id, label: o.name };
});
}
setState([...resultData]);
}, [data])
if (isLoading) return 'Loading...'
return State;
};

Related

I need to refresh the page to login | React and Axios

I have a problem when I want to log in to the login by entering the email and password. What happens is that when I enter with the correct email and correct password, the animation appears but it stays cycled, and if I refresh the page and try again, now it lets me enter into the application
Here's my login form code:
import axios from "axios";
import { useRef, useState } from "react";
import { storeToken } from "../utils/authServices";
import { useNavigate } from "react-router-dom";
import { useLoading } from "../context/hooks/useLoading";
import { LoginForm } from "../components";
export const Login = () => {
const API_URL = "https://api.app"; //I hide the API for security reasons
const { run } = useLoading();
const [error, setError] = useState(false);
const [errorMessage, setErrorMessage] = useState("");
const navigate = useNavigate();
const correoRef = useRef("");
const passwordRef = useRef("");
const handleSubmit = async (e) => {
e.preventDefault();
const { value: correo } = correoRef.current;
const { value: password } = passwordRef.current;
await axios
.post(`${API_URL}/api/auth/login/`, {
correo,
password,
})
.then((response) => {
storeToken(response.data.token);
run();
setTimeout(() => {
navigate("/nueva-solicitud");
}, 1000);
})
.catch((err) => {
console.log(err.response.data);
setError(true);
setErrorMessage(err.response.data.msg);
});
};
return (
<LoginForm
correoRef={correoRef}
passwordRef={passwordRef}
handleSubmit={handleSubmit}
error={error}
errorMessage={errorMessage}
/>
);
};
import { createContext, useReducer, useContext } from "react";
const initialState = {
loading: false,
alerts: [],
};
const reducers = (state, action) => {
switch (action.type) {
case "LOADING_RUN":
return {
...state,
loading: true,
};
case "LOADING_STOP":
return {
...state,
loading: false,
};
default:
return { ...state };
}
};
const AppContext = createContext();
const AppContextProvider = (props) => {
const [state, dispatch] = useReducer(reducers, initialState);
return <AppContext.Provider value={{ state, dispatch }} {...props} />;
};
const useAppContext = () => useContext(AppContext);
export { AppContextProvider, useAppContext };
import { useMemo } from "react";
import { useAppContext } from "../AppContext";
export const useLoading = () => {
const { dispatch } = useAppContext();
const loading = useMemo(
() => ({
run: () => dispatch({ type: "LOADING_RUN" }),
stop: () => dispatch({ type: "LOADING_STOP" }),
}),
[dispatch]
);
return loading;
};
import jwt_decode from "jwt-decode";
export const storeToken = (token) => {
localStorage.setItem("token", token);
};
export const getToken = (decode = false) => {
const token = localStorage.getItem("token");
if (decode) {
const decoded = jwt_decode(token);
return decoded;
}
return token;
};
export const logout = () => {
localStorage.removeItem("token");
};
How can I log in without refreshing the page?
There's two problems here. One is you're using await with a .then .catch block. Pick one or the other. You're also never calling the stop() dispatch when your async call is complete which appears to be responsible for removing the loader.
Instead of:
const { run } = useLoading();
Use:
const { run, stop } = useLoading();
Then change this:
setTimeout(() => {
navigate("/nueva-solicitud");
}, 1000);
To this:
setTimeout(() => {
navigate("/nueva-solicitud");
stop();
}, 1000);
Although I would just recommend writing the entire promise like this:
try {
run();
const response = await axios
.post(`${API_URL}/api/auth/login/`, {
correo,
password,
});
storeToken(response.data.token);
navigate("/nueva-solicitud");
stop();
} catch (err) {
stop();
console.log(err.response.data);
setError(true);
setErrorMessage(err.response.data.msg);
}

Async Storage function for react native, the get action triggers the set operation

The method I use for React Local storage
import {useState, useEffect, Dispatch, SetStateAction } from 'react';
type Response<T> = [
T,
Dispatch<SetStateAction<T>>,
]
function usePersistedState<T>(key:string, initialState: T): Response<T> {
const [state, setState] = useState(() => {
const storageValue = localStorage.getItem(key);
if (storageValue)
return JSON.parse(storageValue);
else
return initialState
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(state));
}, [state, setState]);
return [state, setState];
}
export default usePersistedState;
The method I use for React Native Async storage
import {useState, useEffect, Dispatch, SetStateAction } from 'react';
type Response<T> = [
T,
Dispatch<SetStateAction<T>>,
]
function useAsyncStorage<T>(key:string, initialState: T): Response<T> {
const [state, setState] = useState(async () => {
const value = await AsyncStorage.getItem(key);
if (value){
return value
}
else {
return initialState
}
});
useEffect(() => {
AsyncStorage.setItem(key, JSON.stringify(state));
}, [state,setState]);
// #ts-ignore
return [state, setState];
}
export default useAsyncStorage;
In the use AsyncStorage function, the get operation triggers the set operation.
const [getData,setData] = useAsyncStorage<DefaultData>("data",blankData);
setData("data");
data has been saved.
getData("data");
data was deleted.
Current Solution
import {useState, useEffect, Dispatch, SetStateAction } from 'react';
import { useAsyncStorage } from "#react-native-async-storage/async-storage";
type Response<T> = [
T,
Dispatch<SetStateAction<T>>,
]
function usePersistedState<T>(key: string, defaultValue: T): Response<T> {
const { getItem, setItem } = useAsyncStorage(key);
const [state, setState] = useState<T>(defaultValue);
useEffect(() => {
(async () => {
const data = await getItem();
setState((data ? JSON.parse(data) : undefined) ?? defaultValue);
})();
}, [key]);
useEffect(() => {
(async () => {
await setItem(JSON.stringify(state));
})();
}, [key, state]);
return [state, setState] as [T, typeof setState];
}
export default usePersistedState;

currentUser uid undefined with getAuth hook

I'm a freshman in college and currently beginning with react and firebase in my free time. There is one thing I don't know why it doesn't works in my project.
const currentUser = useAuth()
const { documents: books } = useCollection("books", ["uid", "==", currentUser.uid])
the problem is that when i console i get ["uid", "==", undefined]
This is my useAuth hook
import { useState, useEffect } from 'react'
import { onAuthStateChanged } from "firebase/auth";
import { auth } from '../firebase/config'; //this is getAuth()
export function useAuth() {
const [currentUser, setCurrentUser] = useState();
useEffect(() => {
const unsub = onAuthStateChanged(auth, (user) => setCurrentUser(user));
return unsub;
}, [])
return currentUser;
}
and this is my hook to collect data from firestore
import { useState, useEffect, useRef } from "react"
import { db } from "../firebase/config" //this is getFirestore()
//firebase imports
import { collection, onSnapshot, query, where} from "firebase/firestore"
export const useCollection = (col, _q) => {
const [error, setError] = useState(null)
const [documents, setDocuments] = useState(null)
//set up query
const q = useRef(_q).current
useEffect(() => {
setError(null)
let ref = collection(db, col)
if (q) {
ref = query(ref, where(...q))
}
const unsub = onSnapshot(ref, (snapshot) => {
let results = []
snapshot.docs.forEach(doc => {
results.push({ id: doc.id, ...doc.data() })
})
setDocuments(results)
}, (err) => {
console.log(err.message)
setError(err.message)
})
return () => unsub()
}, [col, q])
return { documents, error }
}
I thought about something with sync or async, but could not find it.
Would someone have a solution and explain it to me?

React Native AsyncStorage getAllKeys infinite loop

This AsyncStorageHooks react custom hook
import {useState} from 'react';
import AsyncStorage from '#react-native-async-storage/async-storage';
const AsyncStorageHooks = (key, value, mergedValue, keys) => {
const [data, setData] = useState('');
const [error, setError] = useState('');
const storeData = async () => {
try {
if (key && value) {
const jsonValue = JSON.stringify(value);
await AsyncStorage.setItem(key, jsonValue);
setData(jsonValue);
}
} catch (e) {
setError(e);
}
};
const getData = async () => {
try {
if (key) {
const jsonValue = await AsyncStorage.getItem(key);
setData(jsonValue != null ? JSON.parse(jsonValue) : '');
}
} catch (e) {
setError(e);
}
};
const mergeData = async () => {
try {
if (key && value && mergedValue) {
const jsonValue = JSON.stringify(value);
const mergedJsonValue = JSON.stringify(mergedValue);
await AsyncStorage.setItem(key, jsonValue);
await AsyncStorage.mergeItem(key, mergedJsonValue);
}
} catch (e) {
setError(e);
}
};
const removeData = async () => {
try {
if (key) {
await AsyncStorage.removeItem(key);
}
} catch (e) {
setError(e);
}
};
const getAllKeys = async () => {
let allKeys = [];
try {
allKeys = await AsyncStorage.getAllKeys();
setData(allKeys);
} catch (e) {
setError(e);
}
};
return {
data,
storeData,
getData,
removeData,
mergeData,
getAllKeys,
error,
};
};
export default AsyncStorageHooks;
this is my home component
const {data, error, getData, storeData, getAllKeys} =
useAsyncStorage('#word');
getData(); // this is works and use setData
storeData(); // this is works and use setData
getAllKeys();
console.log(data);
this works without any problems, they use the same page in the same state. It doesn't go into an infinite loop. The only getAllKeys dosen't works. Other functions works without any problems.
also side note: setData(allKeys); change to setData(allKeys + ''); or setData(JSON.stringify(allKeys)); stop to infinity loop why is that
You are getting an infinite loop because getAllKeys() is being called again and again. The first time it is called, there is this setData(keys) being called, which re-renders the component, because there is a state change.
When the component re-renders, getAllKeys() is called again, so setData(keys) is called, and it goes for ever. You would wanna use a useEffect to solve this problem, like so:
import {Text, View} from 'react-native';
import React, {useState, useEffect} from 'react';
import AsyncStorage from '#react-native-async-storage/async-storage';
const Home = () => {
const [data, setData] = useState([]);
useEffect(()=>{
const getAllKeys = async () => {
let keys = [];
try {
keys = await AsyncStorage.getAllKeys();
setData(keys);
} catch (e) {
// read key error
}
};
getAllKeys();
},[])
console.log(data);
return (
<View>
<Text>hi</Text>
</View>
);
};
export default Home;

How can I call function in React native context file before another function is called?

I'm trying to get my current position and to get some cafe lists around me.
I made a getLocation function and I import it inside of my Context file CafeContext. However, I can't get the position before getting the cafe list.
It works sometimes when I set the lat/long in the range [37.~~, 125.~~].
This is getLocation
import { useState, useEffect } from "react";
import * as Location from "expo-location";
const getLocation = () => {
const [myX, setMyX] = useState(0);
const [myY, setMyY] = useState(0);
try {
const currentLocation = async () => {
await Location.requestPermissionsAsync();
const coordsObj = await Location.getCurrentPositionAsync();
await setMyY(coordsObj.coords.latitude);
await setMyX(coordsObj.coords.longitude);
};
useEffect(() => {
currentLocation();
}, []);
return { myX, myY };
} catch (err) {
setMyY(37.5572);
setMyX(126.9279);
return { myX, myY };
}
};
export default getLocation;
And this is CafeContext:
import React, { useState, createContext } from "react";
import cafeApi from "../api/cafeApi";
import AsyncStorage from "#react-native-community/async-storage";
import testArray from "../api/testArray.json";
import { navigate } from "../RootNavigation";
import getLocation from "../hooks/getLocation";
const CafeContext = React.createContext();
export const CafeProvider = ({ children }) => {
const [cafeList, setCafeList] = useState([]);
const [errorMessage, setErrorMessage] = useState("");
const [distance, setDistance] = useState(300);
//#####This line. I want to get location before getCafeList...
const { myX, myY } = getLocation();
const getCafeList = async () => {
const response = await cafeApi.get("/search", {
params: {
// category_group_code: "CE7",
x: myX,
y: myY,
radius: distance,
},
});
await setCafeList(response.data);
};
return (
<CafeContext.Provider
value={{
cafeList,
getCafeList,
distance,
setDistance,
term,
setTerm,
searchCafeList,
getLikedCafeList,
}}
>
{children}
</CafeContext.Provider>
);
};
export default CafeContext;
Your logic in getLocation is wrong. You are using hooks incorrectly, you can't return a values from a component.
If you want to return values you should create a custom hook also stop awaiting setState functions.
Custom hooks.
import { useState, useEffect } from 'react';
function useLocation() {
const [myX, setMyX] = useState(37.5572);
const [myY, setMyY] = useState(126.9279);
useEffect(() => {
const currentLocation = async () => {
await Location.requestPermissionsAsync();
const coordsObj = await Location.getCurrentPositionAsync();
setMyY(coordsObj.coords.latitude);
setMyX(coordsObj.coords.longitude);
};
currentLocation();
}, []);
return return { myX, myY };
}
Context
const CafeContext = React.createContext();
export const CafeProvider = ({ children }) => {
const [cafeList, setCafeList] = useState([]);
const [errorMessage, setErrorMessage] = useState("");
const [distance, setDistance] = useState(300);
//#####This line. I want to get location before getCafeList...
const { myX, myY } = useLocation();
const getCafeList = async () => {
const response = await cafeApi.get("/search", {
params: {
// category_group_code: "CE7",
x: myX,
y: myY,
radius: distance,
},
});
setCafeList(response.data);
};
return (
<CafeContext.Provider
value={{
cafeList,
getCafeList,
distance,
setDistance,
term,
setTerm,
searchCafeList,
getLikedCafeList,
}}
>
{children}
</CafeContext.Provider>
);
};
export default CafeContext;

Resources